1
+ import sys
1
2
2
3
from argparse import ArgumentParser
3
4
from datetime import datetime , timedelta
4
5
from inspect import getdoc , signature
5
6
from io import StringIO
6
7
from pathlib import Path
8
+ from shutil import copy as copy_file
7
9
from traceback import format_exception
8
10
9
11
from robot .api import ExecutionResult , ResultVisitor , ResultWriter
10
12
from robot .libraries .BuiltIn import BuiltIn
11
13
from robot .errors import DataError
12
- from yaml import load , FullLoader
14
+ from yaml import load , FullLoader , dump as dump_yaml
13
15
14
- from .config import CONFIG_FILE
15
- from .errors import OxygenException
16
+ from .config import CONFIG_FILE , ORIGINAL_CONFIG_FILE
17
+ from .errors import (OxygenException ,
18
+ InvalidConfigurationException ,
19
+ ResultFileNotFoundException )
16
20
from .robot_interface import RobotInterface
17
21
from .version import VERSION
18
22
@@ -25,17 +29,35 @@ class OxygenCore(object):
25
29
26
30
27
31
def __init__ (self ):
28
- with open (CONFIG_FILE , 'r' ) as infile :
32
+ self ._config = None
33
+ self ._handlers = None
34
+
35
+ @property
36
+ def config (self ):
37
+ if self ._config is None :
38
+ self .load_config (CONFIG_FILE )
39
+ return self ._config
40
+
41
+ def load_config (self , config_file ):
42
+ with open (config_file , 'r' ) as infile :
29
43
self ._config = load (infile , Loader = FullLoader )
30
- self ._handlers = {}
31
- self ._register_handlers ()
44
+
45
+ @property
46
+ def handlers (self ):
47
+ if self ._handlers is None :
48
+ self ._handlers = {}
49
+ self ._register_handlers ()
50
+ return self ._handlers
32
51
33
52
def _register_handlers (self ):
34
- for tool_name , config in self ._config .items ():
35
- handler_class = getattr (__import__ (tool_name ,
36
- fromlist = [config ['handler' ]]),
37
- config ['handler' ])
38
- handler = handler_class (config )
53
+ for tool_name , handler_config in self .config .items ():
54
+ try :
55
+ handler_class = getattr (
56
+ __import__ (tool_name , fromlist = [handler_config ['handler' ]]),
57
+ handler_config ['handler' ])
58
+ except ModuleNotFoundError as e :
59
+ raise InvalidConfigurationException (e )
60
+ handler = handler_class (handler_config )
39
61
self ._handlers [tool_name ] = handler
40
62
41
63
@@ -53,7 +75,7 @@ def __init__(self, data):
53
75
54
76
def visit_test (self , test ):
55
77
failures = []
56
- for handler_type , handler in self ._handlers .items ():
78
+ for handler_type , handler in self .handlers .items ():
57
79
try :
58
80
handler .check_for_keyword (test , self .data )
59
81
except Exception as e :
@@ -182,12 +204,12 @@ def __init__(self):
182
204
def _fetch_handler (self , name ):
183
205
try :
184
206
return next (filter (lambda h : h .keyword == name ,
185
- self ._handlers .values ()))
207
+ self .handlers .values ()))
186
208
except StopIteration :
187
209
raise OxygenException ('No handler for keyword "{}"' .format (name ))
188
210
189
211
def get_keyword_names (self ):
190
- return list (handler .keyword for handler in self ._handlers .values ())
212
+ return list (handler .keyword for handler in self .handlers .values ())
191
213
192
214
def run_keyword (self , name , args , kwargs ):
193
215
handler = self ._fetch_handler (name )
@@ -210,31 +232,72 @@ class OxygenCLI(OxygenCore):
210
232
OxygenCLI is a command line interface to transform one test result file to
211
233
corresponding Robot Framework output.xml
212
234
'''
213
- def parse_args (self , parser ):
235
+ MAIN_LEVEL_CLI_ARGS = {
236
+ # we intentionally define `dest` here so we can filter arguments later
237
+ '--version' : {'action' : 'version' ,
238
+ 'dest' : 'version' },
239
+ '--add-config' : {'type' : Path ,
240
+ 'metavar' : 'FILE' ,
241
+ 'dest' : 'add_config' ,
242
+ 'help' : ('path to YAML file whose content is '
243
+ 'appended to existing Oxygen handler '
244
+ 'configuration' )},
245
+ '--reset-config' : {'action' : 'store_true' ,
246
+ 'dest' : 'reset_config' ,
247
+ 'help' : ('resets the Oxygen handler '
248
+ 'configuration to a pristine, '
249
+ 'as-freshly-installed version' )},
250
+ '--print-config' : {'action' : 'store_true' ,
251
+ 'dest' : 'print_config' ,
252
+ 'help' : ('prints current Oxygen handler '
253
+ 'configuration' )}
254
+ }
255
+ def add_arguments (self , parser ):
256
+ # Add version number here to the arguments as it depends on OxygenCLI
257
+ # being initiated already
258
+ self .MAIN_LEVEL_CLI_ARGS ['--version' ]['version' ] = \
259
+ f'%(prog)s { self .__version__ } '
260
+ for flag , params in self .MAIN_LEVEL_CLI_ARGS .items ():
261
+ parser .add_argument (flag , ** params )
262
+
214
263
subcommands = parser .add_subparsers ()
215
- for tool_name , tool_handler in self ._handlers .items ():
264
+ for tool_name , tool_handler in self .handlers .items ():
216
265
subcommand_parser = subcommands .add_parser (tool_name )
217
266
for flags , params in tool_handler .cli ().items ():
218
267
subcommand_parser .add_argument (* flags , ** params )
219
268
subcommand_parser .set_defaults (func = tool_handler .parse_results )
269
+
270
+ def parse_args (self , parser ):
220
271
return vars (parser .parse_args ()) # returns a dictionary
221
272
222
273
def get_output_filename (self , result_file ):
274
+ if result_file is None :
275
+ raise ResultFileNotFoundException ('You did not give any result '
276
+ 'file to convert' )
223
277
filename = Path (result_file )
224
278
filename = filename .with_suffix ('.xml' )
225
279
robot_name = filename .stem + '_robot_output' + filename .suffix
226
280
filename = filename .with_name (robot_name )
227
281
return str (filename )
228
282
229
- def run (self ):
230
- parser = ArgumentParser (prog = 'oxygen' )
231
- parser .add_argument ('--version' ,
232
- action = 'version' ,
233
- version = f'%(prog)s { self .__version__ } ' )
234
- args = self .parse_args (parser )
235
- if not args :
236
- parser .error ('No arguments given' )
237
- output_filename = self .get_output_filename (args ['result_file' ])
283
+ def append_config (self , new_config_path ):
284
+ with open (new_config_path , 'r' ) as new_config :
285
+ with open (CONFIG_FILE , 'a' ) as old_config :
286
+ old_config .write (new_config .read ())
287
+ self .load_config (CONFIG_FILE )
288
+
289
+ @staticmethod
290
+ def reset_config ():
291
+ copy_file (ORIGINAL_CONFIG_FILE , CONFIG_FILE )
292
+ OxygenCLI ().load_config (CONFIG_FILE )
293
+ print ('Oxygen handler configuration reset!' )
294
+
295
+ def print_config (self ):
296
+ print (f'Using config file: { CONFIG_FILE } ' )
297
+ print (dump_yaml (self .config ))
298
+
299
+ def convert_to_robot_result (self , args ):
300
+ output_filename = self .get_output_filename (args .get ('result_file' ))
238
301
parsed_results = args ['func' ](
239
302
** {k : v for (k , v ) in args .items () if not callable (v )})
240
303
robot_suite = RobotInterface ().running .build_suite (parsed_results )
@@ -243,6 +306,38 @@ def run(self):
243
306
report = None ,
244
307
stdout = StringIO ())
245
308
309
+ def run (self ):
310
+ parser = ArgumentParser (prog = 'oxygen' )
311
+ self .add_arguments (parser )
312
+ args = self .parse_args (parser )
313
+ match args :
314
+ case {'add_config' : new_config_path } if new_config_path is not None :
315
+ return self .append_config (new_config_path )
316
+ case {'print_config' : should_print } if should_print :
317
+ return self .print_config ()
318
+ case {'add_config' : _,
319
+ 'reset_config' : _,
320
+ 'print_config' : _,
321
+ ** rest } if not rest : # user is not trying to invoke main-level arguments, but do not provide other arguments either
322
+ parser .error ('No arguments given' )
323
+ case _:
324
+ # filter out arguments meant for other cases so that downstream
325
+ # handler does not need to know about them
326
+ filter_list = [v ['dest' ] for v in
327
+ self .MAIN_LEVEL_CLI_ARGS .values ()]
328
+ filtered_args = {k : v for k , v in args .items ()
329
+ if k not in filter_list }
330
+ return self .convert_to_robot_result (filtered_args )
331
+
332
+ def main ():
333
+ '''Main CLI entrypoint
334
+
335
+ Also used in __main__.py
336
+ '''
337
+ if '--reset-config' in sys .argv :
338
+ OxygenCLI .reset_config ()
339
+ sys .exit (0 )
340
+ OxygenCLI ().run ()
246
341
247
342
if __name__ == '__main__' :
248
- OxygenCLI (). run ()
343
+ main ()
0 commit comments