Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # Copyright (c) 2011 Google Inc. All rights reserved. |
| 3 | # |
| 4 | # Redistribution and use in source and binary forms, with or without |
| 5 | # modification, are permitted provided that the following conditions are |
| 6 | # met: |
| 7 | # |
| 8 | # * Redistributions of source code must retain the above copyright |
| 9 | # notice, this list of conditions and the following disclaimer. |
| 10 | # * Redistributions in binary form must reproduce the above |
| 11 | # copyright notice, this list of conditions and the following disclaimer |
| 12 | # in the documentation and/or other materials provided with the |
| 13 | # distribution. |
| 14 | # * Neither the name of Google Inc. nor the names of its |
| 15 | # contributors may be used to endorse or promote products derived from |
| 16 | # this software without specific prior written permission. |
| 17 | # |
| 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 19 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 20 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 21 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 22 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 23 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 24 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 25 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 26 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 27 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 29 | |
| 30 | import os |
Alexey Kozyatinskiy | a8011e0 | 2018-04-17 17:49:38 +0000 | [diff] [blame] | 31 | import os.path as path |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 32 | import re |
Alexey Kozyatinskiy | a8011e0 | 2018-04-17 17:49:38 +0000 | [diff] [blame] | 33 | import sys |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 34 | try: |
| 35 | import json |
| 36 | except ImportError: |
| 37 | import simplejson as json |
| 38 | |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 39 | sys.path.append(path.dirname(path.abspath(__file__))) |
| 40 | |
| 41 | from devtools_paths import third_party_path |
| 42 | |
| 43 | sys.path.append(path.join(third_party_path(), 'inspector_protocol')) |
Alexey Kozyatinskiy | a8011e0 | 2018-04-17 17:49:38 +0000 | [diff] [blame] | 44 | import pdl # pylint: disable=F0401 |
| 45 | |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 46 | type_traits = { |
| 47 | "any": "*", |
| 48 | "string": "string", |
Johannes Henkel | ff4efae | 2018-10-16 17:55:58 +0000 | [diff] [blame] | 49 | "binary": "string", |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 50 | "integer": "number", |
| 51 | "number": "number", |
| 52 | "boolean": "boolean", |
| 53 | "array": "!Array<*>", |
| 54 | "object": "!Object", |
| 55 | } |
| 56 | |
| 57 | ref_types = {} |
| 58 | |
| 59 | |
| 60 | def full_qualified_type_id(domain_name, type_id): |
| 61 | if type_id.find(".") == -1: |
| 62 | return "%s.%s" % (domain_name, type_id) |
| 63 | return type_id |
| 64 | |
| 65 | |
| 66 | def fix_camel_case(name): |
| 67 | prefix = "" |
| 68 | if name[0] == "-": |
| 69 | prefix = "Negative" |
| 70 | name = name[1:] |
| 71 | refined = re.sub(r'-(\w)', lambda pat: pat.group(1).upper(), name) |
| 72 | refined = to_title_case(refined) |
| 73 | return prefix + re.sub(r'(?i)HTML|XML|WML|API', lambda pat: pat.group(0).upper(), refined) |
| 74 | |
| 75 | |
| 76 | def to_title_case(name): |
| 77 | return name[:1].upper() + name[1:] |
| 78 | |
| 79 | |
| 80 | def generate_enum(name, json): |
| 81 | enum_members = [] |
| 82 | for member in json["enum"]: |
| 83 | enum_members.append(" %s: \"%s\"" % (fix_camel_case(member), member)) |
| 84 | return "\n/** @enum {string} */\n%s = {\n%s\n};\n" % (name, (",\n".join(enum_members))) |
| 85 | |
| 86 | |
| 87 | def param_type(domain_name, param): |
| 88 | if "type" in param: |
| 89 | if param["type"] == "array": |
| 90 | items = param["items"] |
| 91 | return "!Array<%s>" % param_type(domain_name, items) |
| 92 | else: |
| 93 | return type_traits[param["type"]] |
| 94 | if "$ref" in param: |
| 95 | type_id = full_qualified_type_id(domain_name, param["$ref"]) |
| 96 | if type_id in ref_types: |
| 97 | return ref_types[type_id] |
| 98 | else: |
| 99 | print "Type not found: " + type_id |
| 100 | return "!! Type not found: " + type_id |
| 101 | |
| 102 | |
| 103 | def param_name(param): |
| 104 | name = param["name"] |
| 105 | return name if name != "arguments" else "_arguments" |
| 106 | |
| 107 | |
| 108 | def load_schema(file, domains): |
| 109 | input_file = open(file, "r") |
Alexey Kozyatinskiy | a8011e0 | 2018-04-17 17:49:38 +0000 | [diff] [blame] | 110 | parsed_json = pdl.loads(input_file.read(), file) |
| 111 | input_file.close() |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 112 | domains.extend(parsed_json["domains"]) |
| 113 | |
| 114 | |
| 115 | def generate_protocol_externs(output_path, file1, file2): |
| 116 | domains = [] |
| 117 | load_schema(file1, domains) |
| 118 | load_schema(file2, domains) |
| 119 | output_file = open(output_path, "w") |
| 120 | |
| 121 | for domain in domains: |
| 122 | domain_name = domain["domain"] |
| 123 | if "types" in domain: |
| 124 | for type in domain["types"]: |
| 125 | type_id = full_qualified_type_id(domain_name, type["id"]) |
| 126 | ref_types[type_id] = "Protocol.%s.%s" % (domain_name, type["id"]) |
| 127 | |
| 128 | for domain in domains: |
| 129 | domain_name = domain["domain"] |
| 130 | |
| 131 | output_file.write("Protocol.%s = {};\n" % domain_name) |
| 132 | output_file.write("\n\n/**\n * @constructor\n*/\n") |
| 133 | output_file.write("Protocol.%sAgent = function(){};\n" % domain_name) |
| 134 | |
| 135 | if "commands" in domain: |
| 136 | for command in domain["commands"]: |
| 137 | output_file.write("\n/**\n") |
| 138 | params = [] |
| 139 | in_param_to_type = {} |
| 140 | out_param_to_type = {} |
| 141 | has_return_value = "returns" in command |
| 142 | if "parameters" in command: |
| 143 | for in_param in command["parameters"]: |
| 144 | in_param_name = param_name(in_param) |
| 145 | if "optional" in in_param: |
| 146 | in_param_to_type[in_param_name] = "(%s|undefined)" % param_type(domain_name, in_param) |
| 147 | params.append("opt_%s" % in_param_name) |
| 148 | output_file.write(" * @param {%s=} opt_%s\n" % (param_type(domain_name, in_param), in_param_name)) |
| 149 | else: |
| 150 | in_param_to_type[in_param_name] = param_type(domain_name, in_param) |
| 151 | params.append(in_param_name) |
| 152 | output_file.write(" * @param {%s} %s\n" % (param_type(domain_name, in_param), in_param_name)) |
| 153 | returns = [] |
| 154 | returns.append("?Protocol.Error") |
| 155 | if ("error" in command): |
| 156 | returns.append("%s=" % param_type(domain_name, command["error"])) |
| 157 | if (has_return_value): |
| 158 | for out_param in command["returns"]: |
| 159 | out_param_type = param_type(domain_name, out_param) |
| 160 | out_param_to_type[out_param["name"]] = out_param_type |
| 161 | if ("optional" in out_param): |
| 162 | returns.append("%s=" % out_param_type) |
| 163 | else: |
| 164 | returns.append("%s" % out_param_type) |
| 165 | |
| 166 | if has_return_value and len(command["returns"]) > 0: |
| 167 | out_param_type = param_type(domain_name, command["returns"][0]) |
| 168 | if re.match(r"^[!?]", out_param_type[:1]): |
| 169 | out_param_type = out_param_type[1:] |
| 170 | out_param_type = "?%s" % out_param_type |
| 171 | else: |
| 172 | out_param_type = "undefined" |
| 173 | output_file.write(" * @return {!Promise<%s>}\n" % out_param_type) |
| 174 | |
| 175 | output_file.write(" */\n") |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 176 | output_file.write( |
| 177 | "Protocol.%sAgent.prototype.%s = function(%s) {};\n" % (domain_name, command["name"], ", ".join(params))) |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 178 | |
| 179 | request_object_properties = [] |
| 180 | request_type = "Protocol.%sAgent.%sRequest" % (domain_name, to_title_case(command["name"])) |
| 181 | for param in in_param_to_type: |
| 182 | request_object_properties.append("%s: %s" % (param, in_param_to_type[param])) |
| 183 | if request_object_properties: |
| 184 | output_file.write("/** @typedef {!{%s}} */\n" % (", ".join(request_object_properties))) |
| 185 | else: |
| 186 | output_file.write("/** @typedef {Object|undefined} */\n") |
| 187 | output_file.write("%s;\n" % request_type) |
| 188 | |
| 189 | response_object_properties = [] |
| 190 | response_type = "Protocol.%sAgent.%sResponse" % (domain_name, to_title_case(command["name"])) |
| 191 | for param in out_param_to_type: |
| 192 | response_object_properties.append("%s: %s" % (param, out_param_to_type[param])) |
| 193 | if response_object_properties: |
| 194 | output_file.write("/** @typedef {!{%s}} */\n" % (", ".join(response_object_properties))) |
| 195 | else: |
| 196 | output_file.write("/** @typedef {Object|undefined} */\n") |
| 197 | output_file.write("%s;\n" % response_type) |
| 198 | |
| 199 | output_file.write("/**\n") |
| 200 | output_file.write(" * @param {!%s} obj\n" % request_type) |
| 201 | output_file.write(" * @return {!Promise<!%s>}" % response_type) |
| 202 | output_file.write(" */\n") |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 203 | output_file.write("Protocol.%sAgent.prototype.invoke_%s = function(obj) {};\n" % (domain_name, command["name"])) |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 204 | |
| 205 | if "types" in domain: |
| 206 | for type in domain["types"]: |
| 207 | if type["type"] == "object": |
| 208 | typedef_args = [] |
| 209 | if "properties" in type: |
| 210 | for property in type["properties"]: |
| 211 | suffix = "" |
| 212 | if ("optional" in property): |
| 213 | suffix = "|undefined" |
| 214 | if "enum" in property: |
| 215 | enum_name = "Protocol.%s.%s%s" % (domain_name, type["id"], to_title_case(property["name"])) |
| 216 | output_file.write(generate_enum(enum_name, property)) |
| 217 | typedef_args.append("%s:(%s%s)" % (property["name"], enum_name, suffix)) |
| 218 | else: |
| 219 | typedef_args.append("%s:(%s%s)" % (property["name"], param_type(domain_name, property), suffix)) |
| 220 | if (typedef_args): |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 221 | output_file.write( |
| 222 | "\n/** @typedef {!{%s}} */\nProtocol.%s.%s;\n" % (", ".join(typedef_args), domain_name, type["id"])) |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 223 | else: |
| 224 | output_file.write("\n/** @typedef {!Object} */\nProtocol.%s.%s;\n" % (domain_name, type["id"])) |
| 225 | elif type["type"] == "string" and "enum" in type: |
| 226 | output_file.write(generate_enum("Protocol.%s.%s" % (domain_name, type["id"]), type)) |
| 227 | elif type["type"] == "array": |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 228 | output_file.write("\n/** @typedef {!Array<!%s>} */\nProtocol.%s.%s;\n" % (param_type( |
| 229 | domain_name, type["items"]), domain_name, type["id"])) |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 230 | else: |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 231 | output_file.write( |
| 232 | "\n/** @typedef {%s} */\nProtocol.%s.%s;\n" % (type_traits[type["type"]], domain_name, type["id"])) |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 233 | |
Alexey Kozyatinskiy | fac775d | 2018-06-01 22:18:50 +0000 | [diff] [blame] | 234 | if domain_name in ["Runtime", "Debugger", "HeapProfiler"]: |
| 235 | output_file.write("/** @constructor */\n") |
| 236 | else: |
| 237 | output_file.write("/** @interface */\n") |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 238 | output_file.write("Protocol.%sDispatcher = function() {};\n" % domain_name) |
| 239 | if "events" in domain: |
| 240 | for event in domain["events"]: |
| 241 | params = [] |
| 242 | if ("parameters" in event): |
| 243 | output_file.write("/**\n") |
| 244 | for param in event["parameters"]: |
| 245 | if ("optional" in param): |
| 246 | params.append("opt_%s" % param["name"]) |
| 247 | output_file.write(" * @param {%s=} opt_%s\n" % (param_type(domain_name, param), param["name"])) |
| 248 | else: |
| 249 | params.append(param["name"]) |
| 250 | output_file.write(" * @param {%s} %s\n" % (param_type(domain_name, param), param["name"])) |
| 251 | output_file.write(" */\n") |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 252 | output_file.write( |
| 253 | "Protocol.%sDispatcher.prototype.%s = function(%s) {};\n" % (domain_name, event["name"], ", ".join(params))) |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 254 | |
| 255 | for domain in domains: |
| 256 | domain_name = domain["domain"] |
| 257 | uppercase_length = 0 |
| 258 | while uppercase_length < len(domain_name) and domain_name[uppercase_length].isupper(): |
| 259 | uppercase_length += 1 |
| 260 | |
| 261 | output_file.write("/** @return {!Protocol.%sAgent}*/\n" % domain_name) |
| 262 | output_file.write("Protocol.TargetBase.prototype.%s = function(){};\n" % |
| 263 | (domain_name[:uppercase_length].lower() + domain_name[uppercase_length:] + "Agent")) |
| 264 | |
| 265 | output_file.write("/**\n * @param {!Protocol.%sDispatcher} dispatcher\n */\n" % domain_name) |
| 266 | output_file.write("Protocol.TargetBase.prototype.register%sDispatcher = function(dispatcher) {}\n" % domain_name) |
| 267 | |
| 268 | output_file.close() |
| 269 | |
| 270 | |
| 271 | if __name__ == "__main__": |
| 272 | import sys |
| 273 | import os.path |
| 274 | program_name = os.path.basename(__file__) |
| 275 | if len(sys.argv) < 5 or sys.argv[1] != "-o": |
| 276 | sys.stderr.write("Usage: %s -o OUTPUT_FILE INPUT_FILE_1 INPUT_FILE_2\n" % program_name) |
| 277 | exit(1) |
| 278 | output_path = sys.argv[2] |
| 279 | input_path_1 = sys.argv[3] |
| 280 | input_path_2 = sys.argv[4] |
| 281 | generate_protocol_externs(output_path, input_path_1, input_path_2) |