15
15
"CodeGen" ,
16
16
]
17
17
18
- T = TypeVar ('T' )
18
+ T = TypeVar ("T" )
19
19
Signature = Union [ProblemSignature , InteractiveProblemSignature ]
20
20
Code = List [str ]
21
21
@@ -24,32 +24,34 @@ class CodeGen(abc.ABC):
24
24
@property
25
25
@abc .abstractmethod
26
26
def language (self ) -> str :
27
- r """Name of the language to generate."""
27
+ """Name of the language to generate."""
28
28
raise NotImplementedError
29
29
30
30
@property
31
31
def extra_files (self ) -> Dict [str , str ]:
32
- r"""Extra files that will be written verbatim under the project folder. The returned dictionary maps file names
33
- to raw code.
32
+ """
33
+ Extra files that will be written verbatim under the project folder. The returned
34
+ dictionary maps file names to raw code.
34
35
"""
35
36
return {}
36
37
37
38
@property
38
39
@abc .abstractmethod
39
40
def code_extension (self ) -> str :
40
- r """The file extension for code files."""
41
+ """The file extension for code files."""
41
42
raise NotImplementedError
42
43
43
44
@property
44
45
@abc .abstractmethod
45
46
def line_comment_symbol (self ) -> str :
46
- r """The symbol for starting a line comment."""
47
+ """The symbol for starting a line comment."""
47
48
raise NotImplementedError
48
49
49
50
@property
50
51
@abc .abstractmethod
51
52
def template_code (self ) -> str :
52
- r"""The template code for each problem. Should include section markers for:
53
+ """
54
+ The template code for each problem. Should include section markers for:
53
55
54
56
- ``SOLUTION CLASS``: the section to insert the solution class.
55
57
- ``SUBMIT``: the section to submit to LeetCode.
@@ -64,48 +66,68 @@ def template_code(self) -> str:
64
66
65
67
@property
66
68
def user_template_code (self ) -> str :
67
- r"""User-defined templates for convenience. These will be included in the submission."""
69
+ """
70
+ User-defined templates for convenience. These will be included in the
71
+ submission.
72
+ """
68
73
return ""
69
74
70
75
@classmethod
71
76
def write_and_backup (cls , path : str , contents : str ) -> None :
72
- r"""Check if there is already a file at the given path, create a backup if there is, and then write contents to
73
- the file.
77
+ """
78
+ Check if there is already a file at the given path, create a backup if there is,
79
+ and then write contents to the file.
74
80
"""
75
81
if os .path .exists (path ):
76
82
with open (path , "r" ) as f :
77
83
original_contents = f .read ()
78
84
if original_contents != contents :
79
85
# Only create backup if contents differ.
80
86
creation_time = os .path .getctime (path )
81
- timestamp = datetime .fromtimestamp (creation_time ).strftime ("%Y%m%d_%H%M%S" )
87
+ timestamp = datetime .fromtimestamp (creation_time ).strftime (
88
+ "%Y%m%d_%H%M%S"
89
+ )
82
90
83
91
file_name , file_ext = os .path .splitext (path )
84
92
dest_path = f"{ file_name } _{ timestamp } { file_ext } "
85
93
shutil .move (path , dest_path )
86
- log (f"File '{ path } ' is modified, backup created at '{ dest_path } '" , "warning" )
94
+ log (
95
+ f"File '{ path } ' is modified, backup created at '{ dest_path } '" ,
96
+ level = "warning" ,
97
+ )
87
98
with open (path , "w" ) as f :
88
99
f .write (contents )
89
100
90
- def replace_section (self , code : Code , replacements : Dict [str , Code ], * , ignore_errors : bool = False ) -> Code :
91
- r"""Replace a section of template with actual code. Sections are often marked with line comments.
101
+ def replace_section (
102
+ self , code : Code , replacements : Dict [str , Code ], * , ignore_errors : bool = False
103
+ ) -> Code :
104
+ """
105
+ Replace a section of template with actual code. Sections are often marked with
106
+ line comments.
92
107
93
108
:param code: The code as a list of strings, one per line.
94
109
:param replacements: A dictionary mapping section names to replacement code.
95
- :param ignore_errors: A :exc:`ValueError` will be thrown for sections that are not found, unless this argument
96
- is ``True``. Defaults to ``False``.
110
+ :param ignore_errors: A :exc:`ValueError` will be thrown for sections that are
111
+ not found, unless this argument is ``True``. Defaults to
112
+ ``False``.
97
113
:return: The updated code.
98
114
"""
99
115
for section_name , section_code in replacements .items ():
100
116
try :
101
- start_line = code .index (f"{ self .line_comment_symbol } BEGIN { section_name } " )
102
- end_line = code .index (f"{ self .line_comment_symbol } END { section_name } " , start_line + 1 )
117
+ start_line = code .index (
118
+ f"{ self .line_comment_symbol } BEGIN { section_name } "
119
+ )
120
+ end_line = code .index (
121
+ f"{ self .line_comment_symbol } END { section_name } " , start_line + 1
122
+ )
103
123
# exclude the line comments
104
- code = code [:start_line ] + section_code + code [(end_line + 1 ):]
124
+ code = code [:start_line ] + section_code + code [(end_line + 1 ) :]
105
125
except ValueError :
106
126
if not ignore_errors :
107
- raise ValueError (f"Section '{ section_name } ' not found in template code for { self .language } "
108
- f"({ self .__class__ !r} )" )
127
+ raise ValueError (
128
+ f"Section { section_name !r} not found in template code for"
129
+ f" { self .language } ({ self .__class__ !r} )"
130
+ )
109
131
return code
110
132
111
133
@classmethod
@@ -118,23 +140,30 @@ def list_join(cls, list_xs: Iterable[List[T]], sep: List[T]) -> List[T]:
118
140
return ret
119
141
120
142
@abc .abstractmethod
121
- def generate_code (self , problem : Problem , signature : Signature ) -> Tuple [Code , Code ]:
122
- r"""Generate code given the signature. Code consists of two parts:
143
+ def generate_code (
144
+ self , problem : Problem , signature : Signature
145
+ ) -> Tuple [Code , Code ]:
146
+ """
147
+ Generate code given the signature. Code consists of two parts:
123
148
124
149
- Code for the solution class. This is basically the template as-is.
125
- - Code for testing the solution. This includes test functions for each example, and also the main function where
126
- the test functions are called and results are compared.
150
+ - Code for testing the solution. This includes test functions for each example,
151
+ and also the main function where the test functions are called and results are
152
+ compared.
127
153
128
154
:param problem: The crawled raw description of the problem.
129
155
:param signature: The parsed signature of the problem.
130
- :return: A tuple of two lists of strings, corresponding to code for the solution class, and code for testing.
156
+ :return: A tuple of two lists of strings, corresponding to code for the solution
157
+ class, and code for testing.
131
158
"""
132
159
raise NotImplementedError
133
160
134
- def generate_additional_files (self , project_path : str , problems : List [Problem ],
135
- signatures : List [Signature ]) -> None :
136
- r"""Generate additional files that the project requires, besides those in :attr:`EXTRA_FILES` that are written
137
- verbatim.
161
+ def generate_additional_files (
162
+ self , project_path : str , problems : List [Problem ], signatures : List [Signature ]
163
+ ) -> None :
164
+ """
165
+ Generate additional files that the project requires, besides those in
166
+ :attr:`EXTRA_FILES` that are written verbatim.
138
167
139
168
:param project_path: Path to the project folder.
140
169
:param problems: List of problem descriptions to generate code for.
@@ -143,7 +172,9 @@ def generate_additional_files(self, project_path: str, problems: List[Problem],
143
172
pass
144
173
145
174
def get_problem_file_name (self , idx : int , problem : Problem ) -> str :
146
- r"""Generate the code file name for a problem. By default, names are uppercase letters starting from "A".
175
+ """
176
+ Generate the code file name for a problem. By default, names are uppercase
177
+ letters starting from "A".
147
178
148
179
:param idx: Zero-based index of the problem.
149
180
:param problem: The description of the problem.
@@ -152,28 +183,35 @@ def get_problem_file_name(self, idx: int, problem: Problem) -> str:
152
183
return f"{ chr (ord ('A' ) + idx )} { self .code_extension } "
153
184
154
185
def format_statement (self , problem : Problem ) -> List [str ]:
155
- r"""Convert the problem statement into code (as comments).
186
+ """
187
+ Convert the problem statement into code (as comments).
156
188
157
189
:param problem: The problem description.
158
190
:return: Code for the problem statement.
159
191
"""
160
192
statement = []
161
193
max_length = 80 - (len (self .line_comment_symbol ) + 1 )
162
194
for line in problem .statement .strip ().split ("\n " ):
163
- comments = [f"{ self .line_comment_symbol } { line [i :(i + max_length )]} "
164
- for i in range (0 , len (line ), max_length )]
195
+ comments = [
196
+ f"{ self .line_comment_symbol } { line [i :(i + max_length )]} "
197
+ for i in range (0 , len (line ), max_length )
198
+ ]
165
199
statement .extend (comments )
166
200
return statement
167
201
168
- def create_project (self , project_path : str , problems : List [Problem ], site : str , debug : bool = False ) -> None :
169
- r"""Create the folder for the project and generate code and supporting files.
202
+ def create_project (
203
+ self , project_path : str , problems : List [Problem ], site : str , debug : bool = False
204
+ ) -> None :
205
+ """
206
+ Create the folder for the project and generate code and supporting files.
170
207
171
208
:param project_path: Path to the project folder.
172
209
:param problems: List of problem descriptions to generate code for.
173
- :param site: The LeetCode site where problems are crawled. Different sites may have slightly different syntax
174
- (or language-dependent markings).
175
- :param debug: If ``True``, exceptions will not be caught. This is probably only useful when the ``--debug``
176
- flag is set, in which case the Python debugger is hooked to handle exceptions.
210
+ :param site: The LeetCode site where problems are crawled. Different sites may
211
+ have slightly different syntax (or language-dependent markings).
212
+ :param debug: If ``True``, exceptions will not be caught. This is probably only
213
+ useful when the ``--debug`` flag is set, in which case the Python
214
+ debugger is hooked to handle exceptions.
177
215
"""
178
216
if not os .path .exists (project_path ):
179
217
os .makedirs (project_path )
@@ -186,21 +224,33 @@ def create_project(self, project_path: str, problems: List[Problem], site: str,
186
224
try :
187
225
problem_signature = parse_problem (problem , site )
188
226
signatures .append (problem_signature )
189
- solution_code , test_code = self .generate_code (problem , problem_signature )
190
- problem_code = self .replace_section (template , {
191
- "SOLUTION CLASS" : solution_code ,
192
- "TEST" : test_code ,
193
- })
227
+ solution_code , test_code = self .generate_code (
228
+ problem , problem_signature
229
+ )
230
+ problem_code = self .replace_section (
231
+ template ,
232
+ {
233
+ "SOLUTION CLASS" : solution_code ,
234
+ "TEST" : test_code ,
235
+ },
236
+ )
194
237
if problem .statement != "" :
195
238
statement = self .format_statement (problem )
196
- problem_code = self .replace_section (problem_code , {"STATEMENT" : statement }, ignore_errors = True )
197
- code_path = os .path .join (project_path , self .get_problem_file_name (idx , problem ))
239
+ problem_code = self .replace_section (
240
+ problem_code , {"STATEMENT" : statement }, ignore_errors = True
241
+ )
242
+ code_path = os .path .join (
243
+ project_path , self .get_problem_file_name (idx , problem )
244
+ )
198
245
self .write_and_backup (code_path , "\n " .join (problem_code ) + "\n " )
199
246
except Exception :
200
247
if debug :
201
248
raise
202
249
traceback .print_exc ()
203
- log (f"Exception occurred while processing \" { problem .name } \" " , "error" )
250
+ log (
251
+ f"Exception occurred while processing { problem .name !r} " ,
252
+ level = "error" ,
253
+ )
204
254
205
255
for tmpl_name , tmpl_code in self .extra_files .items ():
206
256
with open (os .path .join (project_path , tmpl_name ), "w" ) as f :
0 commit comments