1- import json
1+ # Copyright (c) Microsoft Corporation.
2+ #
3+ # Licensed under the Apache License, Version 2.0 (the "License");
4+ # you may not use this file except in compliance with the License.
5+ # You may obtain a copy of the License at
6+ #
7+ # http://www.apache.org/licenses/LICENSE-2.0
8+ #
9+ # Unless required by applicable law or agreed to in writing, software
10+ # distributed under the License is distributed on an "AS IS" BASIS,
11+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ # See the License for the specific language governing permissions and
13+ # limitations under the License.
14+
215import re
3- from typing import Dict , List
16+ from sys import stderr
17+ from typing import Any , Dict , List , cast
418
519import requests
620
@@ -26,44 +40,91 @@ def load(self) -> None:
2640 class_name = None
2741 method_name = None
2842 in_a_code_block = False
43+ in_options = False
44+ pending_empty_line = False
45+
2946 for line in api_md .split ("\n " ):
30- matches = re .search (r"(class: (\w+)|(Playwright) module)" , line )
31- if matches :
32- class_name = matches .group (2 ) or matches .group (3 )
33- method_name = None
34- if class_name :
35- if class_name not in self .documentation :
36- self .documentation [class_name ] = {}
37- matches = re .search (r"#### \w+\.(.+?)(\(|$)" , line )
38- if matches :
39- method_name = matches .group (1 )
40- # Skip heading
41- continue
4247 if "```js" in line :
4348 in_a_code_block = True
4449 elif "```" in line :
4550 in_a_code_block = False
46- elif method_name and not in_a_code_block :
47- if method_name not in self .documentation [class_name ]: # type: ignore
51+ continue
52+ if in_a_code_block :
53+ continue
54+
55+ if line .startswith ("### " ):
56+ class_name = None
57+ method_name = None
58+ match = re .search (r"### class: (\w+)" , line ) or re .search (
59+ r"### Playwright module" , line
60+ )
61+ if match :
62+ class_name = match .group (1 ) if match .groups () else "Playwright"
63+ self .documentation [class_name ] = {} # type: ignore
64+ continue
65+ if line .startswith ("#### " ):
66+ match = re .search (r"#### (\w+)\.(.+?)(\(|$)" , line )
67+ if match :
68+ if not class_name or match .group (1 ).lower () != class_name .lower ():
69+ print ("Error: " + line + " in " + cast (str , class_name ))
70+ method_name = match .group (2 )
71+ pending_empty_line = False
4872 self .documentation [class_name ][method_name ] = [] # type: ignore
49- self .documentation [class_name ][method_name ].append (line ) # type: ignore
50-
51- def _transform_doc_entry (self , entries : List [str ]) -> List [str ]:
52- trimmed = "\n " .join (entries ).strip ().replace ("\\ " , "\\ \\ " )
53- trimmed = re .sub (r"<\[Array\]<\[(.*?)\]>>" , r"<List[\1]>" , trimmed )
54- trimmed = trimmed .replace ("Object" , "Dict" )
55- trimmed = trimmed .replace ("Array" , "List" )
56- trimmed = trimmed .replace ("boolean" , "bool" )
57- trimmed = trimmed .replace ("string" , "str" )
58- trimmed = trimmed .replace ("number" , "int" )
59- trimmed = trimmed .replace ("Buffer" , "bytes" )
60- trimmed = re .sub (r"<\?\[(.*?)\]>" , r"<Optional[\1]>" , trimmed )
61- trimmed = re .sub (r"<\[Promise\]<(.*)>>" , r"<\1>" , trimmed )
62- trimmed = re .sub (r"<\[(\w+?)\]>" , r"<\1>" , trimmed )
63-
64- return trimmed .replace ("\n \n \n " , "\n \n " ).split ("\n " )
65-
66- def print_entry (self , class_name : str , method_name : str ) -> None :
73+ continue
74+
75+ if not method_name : # type: ignore
76+ continue
77+
78+ if (
79+ line .startswith ("- `options` <[Object]>" )
80+ or line .startswith ("- `options` <[string]|[Object]>" )
81+ or line .startswith ("- `overrides` <" )
82+ or line .startswith ("- `response` <" )
83+ ):
84+ in_options = True
85+ continue
86+ if not line .startswith (" " ):
87+ in_options = False
88+ if in_options :
89+ line = line [2 :]
90+ # if not line.strip():
91+ # continue
92+ if "Shortcut for" in line :
93+ continue
94+ if not line .strip ():
95+ pending_empty_line = bool (self .documentation [class_name ][method_name ]) # type: ignore
96+ continue
97+ else :
98+ if pending_empty_line :
99+ pending_empty_line = False
100+ self .documentation [class_name ][method_name ].append ("" ) # type: ignore
101+ self .documentation [class_name ][method_name ].append (line ) # type: ignore
102+
103+ def _transform_doc_entry (self , line : str ) -> str :
104+ line = line .replace ("\\ " , "\\ \\ " )
105+ line = re .sub (r"<\[Array\]<\[(.*?)\]>>" , r"<List[\1]>" , line )
106+ line = line .replace ("Object" , "Dict" )
107+ line = line .replace ("Array" , "List" )
108+ line = line .replace ("boolean" , "bool" )
109+ line = line .replace ("string" , "str" )
110+ line = line .replace ("number" , "int" )
111+ line = line .replace ("Buffer" , "bytes" )
112+ line = re .sub (r"<\?\[(.*?)\]>" , r"<Optional[\1]>" , line )
113+ line = re .sub (r"<\[Promise\]<(.*)>>" , r"<\1>" , line )
114+ line = re .sub (r"<\[(\w+?)\]>" , r"<\1>" , line )
115+
116+ # Following should be fixed in the api.md upstream
117+ line = re .sub (r"- `pageFunction` <[^>]+>" , "- `expression` <[str]>" , line )
118+ line = re .sub ("- `urlOrPredicate`" , "- `url`" , line )
119+ line = re .sub ("- `playwrightBinding`" , "- `binding`" , line )
120+ line = re .sub ("- `playwrightFunction`" , "- `binding`" , line )
121+ line = re .sub ("- `script`" , "- `source`" , line )
122+
123+ return line
124+
125+ def print_entry (
126+ self , class_name : str , method_name : str , signature : Dict [str , Any ] = None
127+ ) -> None :
67128 if class_name == "BindingCall" or method_name == "pid" :
68129 return
69130 if method_name in self .method_name_rewrites :
@@ -75,19 +136,55 @@ def print_entry(self, class_name: str, method_name: str) -> None:
75136 raw_doc = self .documentation ["JSHandle" ][method_name ]
76137 else :
77138 raw_doc = self .documentation [class_name ][method_name ]
139+
78140 ident = " " * 4 * 2
79- doc_entries = self ._transform_doc_entry (raw_doc )
141+
142+ if signature :
143+ if "return" in signature :
144+ del signature ["return" ]
145+
80146 print (f'{ ident } """' )
81- for line in doc_entries :
82- print (f"{ ident } { line } " )
147+
148+ # Validate signature
149+ validate_parameters = True
150+ for line in raw_doc :
151+ if not line .strip ():
152+ validate_parameters = (
153+ False # Stop validating parameters after a blank line
154+ )
155+
156+ transformed = self ._transform_doc_entry (line )
157+ match = re .search (r"^\- `(\w+)`" , transformed )
158+ if validate_parameters and signature and match :
159+ name = match .group (1 )
160+ if name not in signature :
161+ print (
162+ f"Not implemented parameter { class_name } .{ method_name } ({ name } =)" ,
163+ file = stderr ,
164+ )
165+ continue
166+ else :
167+ del signature [name ]
168+ print (f"{ ident } { transformed } " )
169+ if name == "expression" and "force_expr" in signature :
170+ print (
171+ f"{ ident } - `force_expr` <[bool]> Whether to treat given expression as JavaScript evaluate expression, even though it looks like an arrow function"
172+ )
173+ del signature ["force_expr" ]
174+ else :
175+ print (f"{ ident } { transformed } " )
176+
83177 print (f'{ ident } """' )
84178
179+ if signature :
180+ print (
181+ f"Not documented parameters: { class_name } .{ method_name } ({ signature .keys ()} )" ,
182+ file = stderr ,
183+ )
184+
85185
86186if __name__ == "__main__" :
87- print (
88- json .dumps (
89- DocumentationProvider ().documentation ["Page" ].get ("keyboard" ),
90- sort_keys = True ,
91- indent = 4 ,
92- )
93- )
187+ DocumentationProvider ().print_entry ("Page" , "goto" )
188+ DocumentationProvider ().print_entry ("Page" , "evaluateHandle" )
189+ DocumentationProvider ().print_entry ("ElementHandle" , "click" )
190+ DocumentationProvider ().print_entry ("Page" , "screenshot" )
0 commit comments