20
20
21
21
22
22
def strip_comments (line : str ) -> str :
23
- """Remove comments from a line of text."""
23
+ """Remove comments from a line of text.
24
+
25
+ Args:
26
+ line (str): The line of text from which to remove comments.
27
+
28
+ Returns:
29
+ str: The line of text without comments, with leading and trailing whitespace removed.
30
+ """
24
31
if res := COMMENT_RE .search (line ):
25
32
line = line [: res .start ()]
26
33
return line .strip ()
27
34
28
35
29
36
def parse_feature (basedir : str , filename : str , encoding : str = "utf-8" ) -> Feature :
30
- """Parse a feature file into a Feature object."""
37
+ """Parse a feature file into a Feature object.
38
+
39
+ Args:
40
+ basedir (str): The base directory of the feature file.
41
+ filename (str): The name of the feature file.
42
+ encoding (str): The encoding of the feature file (default is "utf-8").
43
+
44
+ Returns:
45
+ Feature: A Feature object representing the parsed feature file.
46
+
47
+ Raises:
48
+ FeatureError: If there is an error parsing the feature file.
49
+ """
31
50
abs_filename = os .path .abspath (os .path .join (basedir , filename ))
32
51
rel_filename = os .path .join (os .path .basename (basedir ), filename )
33
52
with open (abs_filename , encoding = encoding ) as f :
@@ -46,6 +65,19 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> Featu
46
65
47
66
@dataclass (eq = False )
48
67
class Feature :
68
+ """Represents a feature parsed from a feature file.
69
+
70
+ Attributes:
71
+ scenarios (OrderedDict[str, ScenarioTemplate]): A dictionary of scenarios in the feature.
72
+ filename (str): The absolute path of the feature file.
73
+ rel_filename (str): The relative path of the feature file.
74
+ name (Optional[str]): The name of the feature.
75
+ tags (set[str]): A set of tags associated with the feature.
76
+ background (Optional[Background]): The background steps for the feature, if any.
77
+ line_number (int): The line number where the feature starts in the file.
78
+ description (str): The description of the feature.
79
+ """
80
+
49
81
scenarios : OrderedDict [str , ScenarioTemplate ]
50
82
filename : str
51
83
rel_filename : str
@@ -58,28 +90,70 @@ class Feature:
58
90
59
91
@dataclass (eq = False )
60
92
class Examples :
93
+ """Represents examples used in scenarios for parameterization.
94
+
95
+ Attributes:
96
+ line_number (Optional[int]): The line number where the examples start.
97
+ name (Optional[str]): The name of the examples.
98
+ example_params (List[str]): The names of the parameters for the examples.
99
+ examples (List[Sequence[str]]): The list of example rows.
100
+ """
101
+
61
102
line_number : int | None = None
62
103
name : str | None = None
63
104
example_params : list [str ] = field (default_factory = list )
64
105
examples : list [Sequence [str ]] = field (default_factory = list )
65
106
66
107
def set_param_names (self , keys : Iterable [str ]) -> None :
108
+ """Set the parameter names for the examples.
109
+
110
+ Args:
111
+ keys (Iterable[str]): The parameter names to set.
112
+ """
67
113
self .example_params = [str (key ) for key in keys ]
68
114
69
115
def add_example (self , values : Sequence [str ]) -> None :
116
+ """Add a new example row.
117
+
118
+ Args:
119
+ values (Sequence[str]): The values for the example row.
120
+ """
70
121
self .examples .append ([str (value ) if value is not None else "" for value in values ])
71
122
72
123
def as_contexts (self ) -> Iterable [dict [str , Any ]]:
124
+ """Generate contexts for the examples.
125
+
126
+ Yields:
127
+ Dict[str, Any]: A dictionary mapping parameter names to their values for each example row.
128
+ """
73
129
for row in self .examples :
74
130
assert len (self .example_params ) == len (row )
75
131
yield dict (zip (self .example_params , row ))
76
132
77
133
def __bool__ (self ) -> bool :
134
+ """Check if there are any examples.
135
+
136
+ Returns:
137
+ bool: True if there are examples, False otherwise.
138
+ """
78
139
return bool (self .examples )
79
140
80
141
81
142
@dataclass (eq = False )
82
143
class ScenarioTemplate :
144
+ """Represents a scenario template within a feature.
145
+
146
+ Attributes:
147
+ feature (Feature): The feature to which this scenario belongs.
148
+ name (str): The name of the scenario.
149
+ line_number (int): The line number where the scenario starts in the file.
150
+ templated (bool): Whether the scenario is templated.
151
+ description (Optional[str]): The description of the scenario.
152
+ tags (set[str]): A set of tags associated with the scenario.
153
+ _steps (List[Step]): The list of steps in the scenario (internal use only).
154
+ examples (Optional[Examples]): The examples used for parameterization in the scenario.
155
+ """
156
+
83
157
feature : Feature
84
158
name : str
85
159
line_number : int
@@ -90,14 +164,32 @@ class ScenarioTemplate:
90
164
examples : Examples | None = field (default_factory = Examples )
91
165
92
166
def add_step (self , step : Step ) -> None :
167
+ """Add a step to the scenario.
168
+
169
+ Args:
170
+ step (Step): The step to add.
171
+ """
93
172
step .scenario = self
94
173
self ._steps .append (step )
95
174
96
175
@property
97
176
def steps (self ) -> list [Step ]:
177
+ """Get all steps for the scenario, including background steps.
178
+
179
+ Returns:
180
+ List[Step]: A list of steps, including any background steps from the feature.
181
+ """
98
182
return (self .feature .background .steps if self .feature .background else []) + self ._steps
99
183
100
184
def render (self , context : Mapping [str , Any ]) -> Scenario :
185
+ """Render the scenario with the given context.
186
+
187
+ Args:
188
+ context (Mapping[str, Any]): The context for rendering steps.
189
+
190
+ Returns:
191
+ Scenario: A Scenario object with steps rendered based on the context.
192
+ """
101
193
background_steps = self .feature .background .steps if self .feature .background else []
102
194
scenario_steps = [
103
195
Step (
@@ -122,6 +214,17 @@ def render(self, context: Mapping[str, Any]) -> Scenario:
122
214
123
215
@dataclass (eq = False )
124
216
class Scenario :
217
+ """Represents a scenario with steps.
218
+
219
+ Attributes:
220
+ feature (Feature): The feature to which this scenario belongs.
221
+ name (str): The name of the scenario.
222
+ line_number (int): The line number where the scenario starts in the file.
223
+ steps (List[Step]): The list of steps in the scenario.
224
+ description (Optional[str]): The description of the scenario.
225
+ tags (set[str]): A set of tags associated with the scenario.
226
+ """
227
+
125
228
feature : Feature
126
229
name : str
127
230
line_number : int
@@ -132,6 +235,20 @@ class Scenario:
132
235
133
236
@dataclass (eq = False )
134
237
class Step :
238
+ """Represents a step within a scenario or background.
239
+
240
+ Attributes:
241
+ type (str): The type of step (e.g., 'given', 'when', 'then').
242
+ _name (str): The name of the step.
243
+ line_number (int): The line number where the step starts in the file.
244
+ indent (int): The indentation level of the step.
245
+ keyword (str): The keyword used for the step (e.g., 'Given', 'When', 'Then').
246
+ failed (bool): Whether the step has failed (internal use only).
247
+ scenario (Optional[ScenarioTemplate]): The scenario to which this step belongs (internal use only).
248
+ background (Optional[Background]): The background to which this step belongs (internal use only).
249
+ lines (List[str]): Additional lines for the step (internal use only).
250
+ """
251
+
135
252
type : str
136
253
_name : str
137
254
line_number : int
@@ -143,20 +260,48 @@ class Step:
143
260
lines : list [str ] = field (init = False , default_factory = list )
144
261
145
262
def __init__ (self , name : str , type : str , indent : int , line_number : int , keyword : str ) -> None :
263
+ """Initialize a step.
264
+
265
+ Args:
266
+ name (str): The name of the step.
267
+ type (str): The type of the step (e.g., 'given', 'when', 'then').
268
+ indent (int): The indentation level of the step.
269
+ line_number (int): The line number where the step starts in the file.
270
+ keyword (str): The keyword used for the step (e.g., 'Given', 'When', 'Then').
271
+ """
146
272
self .name = name
147
273
self .type = type
148
274
self .indent = indent
149
275
self .line_number = line_number
150
276
self .keyword = keyword
151
277
152
278
def __str__ (self ) -> str :
279
+ """Return a string representation of the step.
280
+
281
+ Returns:
282
+ str: A string representation of the step.
283
+ """
153
284
return f'{ self .type .capitalize ()} "{ self .name } "'
154
285
155
286
@property
156
287
def params (self ) -> tuple [str , ...]:
288
+ """Get the parameters in the step name.
289
+
290
+ Returns:
291
+ Tuple[str, ...]: A tuple of parameter names found in the step name.
292
+ """
157
293
return tuple (frozenset (STEP_PARAM_RE .findall (self .name )))
158
294
159
295
def render (self , context : Mapping [str , Any ]) -> str :
296
+ """Render the step name with the given context.
297
+
298
+ Args:
299
+ context (Mapping[str, Any]): The context for rendering the step name.
300
+
301
+ Returns:
302
+ str: The rendered step name with parameters replaced by their values from the context.
303
+ """
304
+
160
305
def replacer (m : re .Match ) -> str :
161
306
varname = m .group (1 )
162
307
return str (context .get (varname , f"<missing:{ varname } >" ))
@@ -166,27 +311,75 @@ def replacer(m: re.Match) -> str:
166
311
167
312
@dataclass (eq = False )
168
313
class Background :
314
+ """Represents the background steps for a feature.
315
+
316
+ Attributes:
317
+ feature (Feature): The feature to which this background belongs.
318
+ line_number (int): The line number where the background starts in the file.
319
+ steps (List[Step]): The list of steps in the background.
320
+ """
321
+
169
322
feature : Feature
170
323
line_number : int
171
324
steps : list [Step ] = field (init = False , default_factory = list )
172
325
173
326
def add_step (self , step : Step ) -> None :
327
+ """Add a step to the background.
328
+
329
+ Args:
330
+ step (Step): The step to add.
331
+ """
174
332
step .background = self
175
333
self .steps .append (step )
176
334
177
335
178
336
def dict_to_feature (abs_filename : str , rel_filename : str , data : dict ) -> Feature :
337
+ """Convert a dictionary representation of a feature into a Feature object.
338
+
339
+ Args:
340
+ abs_filename (str): The absolute path of the feature file.
341
+ rel_filename (str): The relative path of the feature file.
342
+ data (dict): The dictionary containing the feature data.
343
+
344
+ Returns:
345
+ Feature: A Feature object representing the parsed feature data.
346
+ """
347
+
179
348
def get_tag_names (tag_data : list [dict ]) -> set [str ]:
349
+ """Extract tag names from tag data.
350
+
351
+ Args:
352
+ tag_data (List[dict]): The tag data to extract names from.
353
+
354
+ Returns:
355
+ set[str]: A set of tag names.
356
+ """
180
357
return {tag ["name" ].lstrip ("@" ) for tag in tag_data }
181
358
182
359
def get_step_type (keyword : str ) -> str | None :
360
+ """Map a step keyword to its corresponding type.
361
+
362
+ Args:
363
+ keyword (str): The keyword for the step (e.g., 'given', 'when', 'then').
364
+
365
+ Returns:
366
+ str | None: The type of the step, or None if the keyword is unknown.
367
+ """
183
368
return {
184
369
"given" : GIVEN ,
185
370
"when" : WHEN ,
186
371
"then" : THEN ,
187
372
}.get (keyword )
188
373
189
374
def parse_steps (steps_data : list [dict ]) -> list [Step ]:
375
+ """Parse a list of step data into Step objects.
376
+
377
+ Args:
378
+ steps_data (List[dict]): The list of step data.
379
+
380
+ Returns:
381
+ List[Step]: A list of Step objects.
382
+ """
190
383
steps = []
191
384
current_step_type = None
192
385
for step_data in steps_data :
@@ -208,6 +401,15 @@ def parse_steps(steps_data: list[dict]) -> list[Step]:
208
401
return steps
209
402
210
403
def parse_scenario (scenario_data : dict , feature : Feature ) -> ScenarioTemplate :
404
+ """Parse a scenario data dictionary into a ScenarioTemplate object.
405
+
406
+ Args:
407
+ scenario_data (dict): The dictionary containing scenario data.
408
+ feature (Feature): The feature to which this scenario belongs.
409
+
410
+ Returns:
411
+ ScenarioTemplate: A ScenarioTemplate object representing the parsed scenario.
412
+ """
211
413
scenario = ScenarioTemplate (
212
414
feature = feature ,
213
415
name = strip_comments (scenario_data ["name" ]),
0 commit comments