2323one line.
2424
2525"""
26+ from os import path as op # pragma: no cover
27+
2628import re # pragma: no cover
2729import sys # pragma: no cover
2830import textwrap
3234
3335
3436class FeatureError (Exception ): # pragma: no cover
37+
3538 """Feature parse error."""
3639
3740 message = u'{0}.\n Line number: {1}.\n Line: {2}.'
@@ -93,18 +96,17 @@ def strip_comments(line):
9396 return line .strip ()
9497
9598
96- def remove_prefix (line ):
97- """Remove the step prefix (Scenario, Given, When, Then or And).
99+ def parse_line (line ):
100+ """Parse step line to get the step prefix (Scenario, Given, When, Then or And) and the actual step name .
98101
99102 :param line: Line of the Feature file.
100103
101- :return: Line without the prefix.
102-
104+ :return: `tuple` in form ('<prefix>', '<Line without the prefix>').
103105 """
104106 for prefix , _ in STEP_PREFIXES :
105107 if line .startswith (prefix ):
106- return line [len (prefix ):].strip ()
107- return line
108+ return prefix . strip (), line [len (prefix ):].strip ()
109+ return '' , line
108110
109111
110112def _open_file (filename , encoding ):
@@ -114,11 +116,19 @@ def _open_file(filename, encoding):
114116 return open (filename , 'r' , encoding = encoding )
115117
116118
117- def force_unicode (string , encoding = 'utf-8' ):
118- if sys .version_info < (3 , 0 ) and isinstance (string , str ):
119- return string .decode (encoding )
119+ def force_unicode (obj , encoding = 'utf-8' ):
120+ """Get the unicode string out of given object (python 2 and python 3).
121+
122+ :param obj: `object`, usually a string
123+ :return: unicode string
124+ """
125+ if sys .version_info < (3 , 0 ):
126+ if isinstance (obj , str ):
127+ return obj .decode (encoding )
128+ else :
129+ return unicode (obj )
120130 else :
121- return string
131+ return str ( obj )
122132
123133
124134def force_encode (string , encoding = 'utf-8' ):
@@ -129,17 +139,20 @@ def force_encode(string, encoding='utf-8'):
129139
130140
131141class Feature (object ):
142+
132143 """Feature."""
133144
134- def __init__ (self , filename , encoding = 'utf-8' ):
145+ def __init__ (self , basedir , filename , encoding = 'utf-8' ):
135146 """Parse the feature file.
136147
137148 :param filename: Relative path to the feature file.
138149
139150 """
140151 self .scenarios = {}
141-
142- self .filename = filename
152+ self .rel_filename = op .join (op .basename (basedir ), filename )
153+ self .filename = filename = op .abspath (op .join (basedir , filename ))
154+ self .line_number = 1
155+ self .name = None
143156 scenario = None
144157 mode = None
145158 prev_mode = None
@@ -181,16 +194,17 @@ def __init__(self, filename, encoding='utf-8'):
181194
182195 if mode == types .FEATURE :
183196 if prev_mode != types .FEATURE :
184- self .name = remove_prefix (clean_line )
197+ _ , self .name = parse_line (clean_line )
198+ self .line_number = line_number
185199 else :
186200 description .append (clean_line )
187201
188202 prev_mode = mode
189203
190204 # Remove Feature, Given, When, Then, And
191- clean_line = remove_prefix (clean_line )
205+ keyword , clean_line = parse_line (clean_line )
192206 if mode in [types .SCENARIO , types .SCENARIO_OUTLINE ]:
193- self .scenarios [clean_line ] = scenario = Scenario (self , clean_line )
207+ self .scenarios [clean_line ] = scenario = Scenario (self , clean_line , line_number )
194208 elif mode == types .EXAMPLES :
195209 mode = types .EXAMPLES_HEADERS
196210 elif mode == types .EXAMPLES_VERTICAL :
@@ -205,14 +219,16 @@ def __init__(self, filename, encoding='utf-8'):
205219 scenario .add_example_row (clean_line [0 ], clean_line [1 :])
206220 elif mode and mode != types .FEATURE :
207221 step = scenario .add_step (
208- step_name = clean_line , step_type = mode , indent = line_indent , line_number = line_number )
222+ step_name = clean_line , step_type = mode , indent = line_indent , line_number = line_number ,
223+ keyword = keyword )
209224
210225 self .description = u'\n ' .join (description )
211226
212227 @classmethod
213- def get_feature (cls , filename , encoding = 'utf-8' ):
228+ def get_feature (cls , base_path , filename , encoding = 'utf-8' ):
214229 """Get a feature by the filename.
215230
231+ :param base_path: Base feature directory.
216232 :param filename: Filename of the feature file.
217233
218234 :return: `Feature` instance from the parsed feature cache.
@@ -222,37 +238,43 @@ def get_feature(cls, filename, encoding='utf-8'):
222238 when multiple scenarios are referencing the same file.
223239
224240 """
225- feature = features .get (filename )
241+ full_name = op .abspath (op .join (base_path , filename ))
242+ feature = features .get (full_name )
226243 if not feature :
227- feature = Feature (filename , encoding = encoding )
228- features [filename ] = feature
244+ feature = Feature (base_path , filename , encoding = encoding )
245+ features [full_name ] = feature
229246 return feature
230247
231248
232249class Scenario (object ):
250+
233251 """Scenario."""
234252
235- def __init__ (self , feature , name , example_converters = None ):
253+ def __init__ (self , feature , name , line_number , example_converters = None ):
236254 self .feature = feature
237255 self .name = name
238256 self .params = set ()
239257 self .steps = []
240258 self .example_params = []
241259 self .examples = []
242260 self .vertical_examples = []
261+ self .line_number = line_number
243262 self .example_converters = example_converters
244263
245- def add_step (self , step_name , step_type , indent , line_number ):
264+ def add_step (self , step_name , step_type , indent , line_number , keyword ):
246265 """Add step to the scenario.
247266
248267 :param step_name: Step name.
249268 :param step_type: Step type.
250-
269+ :param indent: `int` step text indent
270+ :param line_number: `int` line number
271+ :param keyword: `str` step keyword
251272 """
252273 params = get_step_params (step_name )
253274 self .params .update (params )
254275 step = Step (
255- name = step_name , type = step_type , params = params , scenario = self , indent = indent , line_number = line_number )
276+ name = step_name , type = step_type , params = params , scenario = self , indent = indent , line_number = line_number ,
277+ keyword = keyword )
256278 self .steps .append (step )
257279 return step
258280
@@ -326,16 +348,19 @@ def validate(self):
326348
327349
328350class Step (object ):
351+
329352 """Step."""
330353
331- def __init__ (self , name , type , params , scenario , indent , line_number ):
354+ def __init__ (self , name , type , params , scenario , indent , line_number , keyword ):
332355 self .name = name
356+ self .keyword = keyword
333357 self .lines = []
334358 self .indent = indent
335359 self .type = type
336360 self .params = params
337361 self .scenario = scenario
338362 self .line_number = line_number
363+ self .failed = False
339364
340365 def add_line (self , line ):
341366 """Add line to the multiple step."""
0 commit comments