2 # -*- coding: iso-8859-1 -*-
5 # Command line tool to manage SALOME configuration.
6 # Usage: type "scfg help" to learn how to use tool.
11 from sconfig.salome_config import CfgTool, defaultConfFile
15 # 1. Improve parsing of command line: each command should be defined as:
16 # - command: name of the command
17 # - help: help string as appears in the short description of a command in a list
18 # of commands and as a first line of command's help
19 # - options: (dict) see below
20 # - parameters: (list or dict) see below
21 # Each option should be defined with:
22 # - title: one letter signature
24 # - parameter: name of parameter if option requires a parameter
25 # Each parameter should be defined with:
26 # - title: as appears in help information
27 # - optional: flag (boolean) showing if this parameter is optional
28 # - help: help description
30 # Parser of command line should then automatically check validity of the command,
31 # its parameters and options.
33 # 2. Usage and help information should be automaticall generated and formatted
34 # from command description as described above.
36 # 3. Protect from adding same named option to a command.
38 # 4. Improve formatting help for command's parameters (see 1) - namely, wrapping
43 # function: get_tool_name
45 # gets a name of this command line tool (string)
49 return os.path.basename(sys.argv[0])
54 # returns True when in debug mode (env var VERBOSE is set to 1)
58 return bool(os.getenv("VERBOSE", False))
63 # prints debug information (for debug purposes)
66 # args: output data to be printed to the screen (any printable objects)
71 print("[DEBUG]", end=' ')
72 for a in args: print(a, end=' ')
77 # function: error_exit
79 # prints error information and exits with non-zero status
82 # msg: error message (string)
86 print("%s: %s" % (get_tool_name(), msg))
91 # function: get_commands
93 # get supported commands
95 # return value is a dictionary:
96 # { cmd : ( help, opt_map ) }
98 # - cmd is a command (string)
99 # - help is a command's general help information on a command (string)
100 # - opt_map is a disctionary of allowed command's options:
101 # { opt: opt_help }, where
102 # - opt is option ("a:" if option requires a parameter or just "a" elsewhere, where
104 # - opt_help is a help information on the option (string).
106 # See also: add_command
110 if not globals().get("__commands__"): globals()["__commands__"] = {}
111 return globals().get("__commands__")
114 # function: add_command
116 # adds command to the tool
119 # cmd: command (string)
120 # help_string: help information on the command (string)
121 # options: options supported by the command (map {opt: opt_help}, see get_commands)
124 def add_command(cmd, help_string, options):
125 if cmd in get_commands():
126 debug("command %s already exists" % cmd)
128 get_commands()[ cmd ] = ( help_string, options )
134 # gets supported options for the command
137 # cmd: command (string)
139 # return value is map {opt: opt_help} (see get_commands)
143 if cmd is None: cmd = ""
146 opts = get_commands()[cmd][1]
154 # gets help string for the command
157 # cmd: command (string)
159 # return value is unformatted string
163 if cmd is None: cmd = ""
166 help_string = get_commands()[cmd][0]
172 # function: format_string
174 # formats string into block according to the given block width
178 # width: requested width of resulting string block
180 # return formatted string block
183 def format_string(s, width):
184 blocks = s.split("\n")
187 block = block.replace("\t", " [[TAB]] ")
188 words = block.split()
191 word = word if word != "[[TAB]]" else " "*2
192 if not current_string:
193 current_string = word
195 if len(current_string + " " + word) <= width:
196 current_string += " " + word
198 result.append(current_string)
199 current_string = word
203 result.append(current_string)
205 return "\n".join(result)
208 # function: format_commands
210 # formats short help on all supported commands
212 # return string that contains formatted help on commands
215 def format_commands():
216 commands = get_commands()
217 # filter out tool itself
218 commands = sorted([a for a in commands if a != ""])
219 # sort commands alphabetically
220 # get max command's length
221 max_length = max([ len(i) for i in commands])
222 # generate formatting string
225 fmt_string = prefix + "%" + "-%ds" % max_length + separator
227 _h = get_help(_c).split("\n")[0]
228 _h = format_string(_h, 80-(max_length+len(prefix)+len(separator)))
230 _t = prefix + " " * max_length + separator
233 for _i in _h[1:]: _r.append(_t + _i)
235 return "\n".join([ fmt_string % i + _format(i) for i in commands])
238 # function: format_list_options
240 # formats short help on commands's options to be inserted into short help info on a command
243 # cmd: command (string)
245 # return string that contains formatted help
248 def format_list_options(cmd):
250 return "".join([" [%s]" % i for i in opts])
253 # function: format_options
255 # formats help on commands's options to be inserted after short help info on a command
258 # cmd: command (string)
260 # return string that contains formatted help
263 def format_options(cmd):
265 opts_names = list(opts.keys())
266 if not opts: return ""
267 # get max command's length
268 max_length = max([ len(i) for i in opts_names])
269 # generate formatting string
272 fmt_string = prefix + "%" + "-%ds" % max_length + separator
275 _h = format_string(_h, 80-(max_length+len(prefix)+len(separator)))
277 _t = prefix + " " * max_length + separator
280 for _i in _h[1:]: _r.append(_t + _i)
282 return "\n" + "\n".join([ fmt_string % i + _format(i) for i in opts_names]) + "\n"
287 # prints help and exits with zero status
290 # cmd: command (string)
294 if cmd is None: cmd = ""
295 if cmd not in get_commands(): error_exit("unknown command: %s" % cmd)
297 fmt[ "prg" ] = get_tool_name()
298 fmt[ "cmd" ] = get_tool_name() if not cmd else cmd
299 fmt[ "cmd_list" ] = format_commands()
300 fmt[ "opts" ] = format_list_options(cmd)
301 fmt[ "opt_list" ] = format_options(cmd)
302 help_string = get_help(cmd)
303 help_string = help_string.replace("\t", " ")
304 print(help_string.format(**fmt))
309 # function: parse_cmd_line
311 # parses command line; prints error and exits if unsupported command
312 # or option is specified
315 # args: arguments being parsed (list of strings)
318 def parse_cmd_line(args):
319 tool_opts, cmd, cmd_opts, tgt, params = {}, None, {}, None, []
321 commands = get_commands()
326 error_exit("empty option is not allowed")
327 if a.startswith("-"):
328 allowed_opts = list(get_opts(cmd).keys())
329 allowed_opts = dict([(i[1], len(i)>2) for i in allowed_opts])
330 processed_opts = tool_opts if cmd is None else cmd_opts
331 cmd_descr = "" if cmd is None else " for command '%s'" % cmd
337 # "--" format is not supported
338 error_exit("invalid format of option")
339 elif o in allowed_opts:
341 # supported option; requires parameter
343 processed_opts[ o ] = opts
345 elif args and not args[-1].startswith("-") and not args[-1] in commands:
346 processed_opts[ o ] = args.pop()
348 error_exit("option %s%s requires argument" % (o, cmd_descr))
350 # supported option; does not require parameter
351 processed_opts[ o ] = None
356 error_exit("unknown option: -%s%s" % (o, cmd_descr))
361 if cmd not in commands:
362 # unsupported command
363 error_exit("unknown command: %s" % cmd)
370 return tool_opts, cmd, cmd_opts, tgt, params
375 # main entry point of the tool
382 help_string = "Command line tool to manage SALOME configuration.\n\n"
383 help_string += "Usage: {prg}{opts} COMMAND [ARGS]\n"
384 help_string += "{opt_list}\n"
385 help_string += "Supported commands:\n"
386 help_string += "{cmd_list}\n\n"
387 help_string += "See '{prg} help COMMAND' for more information on a specific command."
389 options [ "-s FILE" ] = "Path to the configuration file; default: %s." % defaultConfFile()
395 help_string = "Display help information about this tool.\n\n"
396 help_string += "Usage: {prg} {cmd}{opts}\n"
397 help_string += "{opt_list}"
403 help_string = "Create or modify configuration object.\n\n"
404 help_string += "Usage: {prg} {cmd}{opts} TARGET [PARAM VALUE ...]\n\n"
405 help_string += "\tTARGET: configuration object being created / modified\n"
406 help_string += "\tPARAM: parameter of the target object\n"
407 help_string += "\tVALUE: value to be assigned to the attribute of the target object\n"
408 help_string += "{opt_list}"
414 help_string = "Get parameter of configuration object.\n\n"
415 help_string += "Usage: {prg} {cmd}{opts} TARGET PARAM\n\n"
416 help_string += "\tTARGET: configuration object being inspected\n"
417 help_string += "\tPARAM: parameter of the target object\n"
418 help_string += "{opt_list}"
424 help_string = "Remove configuration object or its attribute(s).\n\n"
425 help_string += "Usage: {prg} {cmd}{opts} TARGET [PARAM ...]\n\n"
426 help_string += "\tTARGET: configuration object\n"
427 help_string += "\tPARAM: attribute of the target object\n"
428 help_string += "{opt_list}"
429 add_command("remove",
434 help_string = "Dump configuration.\n\n"
435 help_string += "Usage: {prg} {cmd}{opts} [TARGET]\n\n"
436 help_string += "\tTARGET: optional configuration object (if not specified,\n"
437 help_string += "\t all configuration is dumped).\n"
438 help_string += "{opt_list}"
444 help_string = "Verify configuration.\n\n"
445 help_string += "Usage: {prg} {cmd}{opts} [TARGET]\n\n"
446 help_string += "\tTARGET: optional configuration object (if not specified,\n"
447 help_string += "\t all configuration is verified).\n"
448 help_string += "{opt_list}"
449 add_command("verify",
454 help_string = "Clean configuration.\n\n"
455 help_string += "Usage: {prg} {cmd}{opts}\n"
456 help_string += "{opt_list}"
463 opts, cmd, cmd_opts, tgt, params = parse_cmd_line(sys.argv[1:])
464 debug("parse command line: options =", opts, "; command =", cmd, "; command options =", cmd_opts, "; target =", tgt, "; arguments =", params)
468 if not cmd or cmd == "help":
474 # check if custom source file is specified
476 src_file = opts["s"] if "s" in opts else None
477 debug("source file:", src_file)
481 cfg_tool = CfgTool(src_file)
482 debug("cfg tool:", cfg_tool)
484 # TODO: we should not check commands in a switch-like block below;
485 # instead processing should be done automatically basing on the
486 # predefined set of commands (TODO)
490 error_exit("%s: target is not specified!" % cmd)
492 error_exit("%s: parameter of target %s is not specified!" % (cmd, tgt))
493 # only one parameter can be read!
495 error_exit("%s: can't read more than one parameter at once!" % cmd)
497 if v is None: return ""
498 elif isinstance(v, list): return " ".join(v)
500 print(_toString(cfg_tool.get(tgt, params[0])))
504 error_exit("%s: target is not specified!" % cmd)
505 # empty parameters list is allowed!
506 cfg_tool.set(tgt, *params)
508 elif cmd == "remove":
510 error_exit("%s: target is not specified!" % cmd)
511 # empty parameters list is allowed!
512 cfg_tool.remove(tgt, *params)
516 error_exit("%s: command does not support parameters!" % cmd)
517 # empty target is allowed!
520 elif cmd == "verify":
522 error_exit("%s: command does not support parameters!" % cmd)
523 # empty target is allowed!
525 if cfg_tool.verify(tgt, errors):
526 msg = " (no products found)" if not cfg_tool.get("cfg", "products") else ""
527 print("Configuration is valid%s." % msg)
529 raise Exception("Configuration is invalid:\n"+ "\n".join(errors))
533 error_exit("%s: command does not support target!" % cmd)
535 error_exit("%s: command does not support parameters!" % cmd)
536 # empty target is allowed!
540 # unknown command: normally we should not go here
541 error_exit("unknown command: %s" % cmd)
544 except Exception as e:
548 if __name__ == "__main__":