2 # -*- coding: iso-8859-1 -*-
4 # Copyright (C) 2016 OPEN CASCADE
6 # This library is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU Lesser General Public
8 # License as published by the Free Software Foundation; either
9 # version 2.1 of the License, or (at your option) any later version.
11 # This library is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # Lesser General Public License for more details.
16 # You should have received a copy of the GNU Lesser General Public
17 # License along with this library; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
24 # Command line tool to manage SALOME configuration.
25 # Usage: type "scfg help" to learn how to use tool.
30 from sconfig.salome_config import CfgTool, defaultConfFile
34 # 1. Improve parsing of command line: each command should be defined as:
35 # - command: name of the command
36 # - help: help string as appears in the short description of a command in a list
37 # of commands and as a first line of command's help
38 # - options: (dict) see below
39 # - parameters: (list or dict) see below
40 # Each option should be defined with:
41 # - title: one letter signature
43 # - parameter: name of parameter if option requires a parameter
44 # Each parameter should be defined with:
45 # - title: as appears in help information
46 # - optional: flag (boolean) showing if this parameter is optional
47 # - help: help description
49 # Parser of command line should then automatically check validity of the command,
50 # its parameters and options.
52 # 2. Usage and help information should be automaticall generated and formatted
53 # from command description as described above.
55 # 3. Protect from adding same named option to a command.
57 # 4. Improve formatting help for command's parameters (see 1) - namely, wrapping
62 # function: get_tool_name
64 # gets a name of this command line tool (string)
68 return os.path.basename(sys.argv[0])
73 # returns True when in debug mode (env var VERBOSE is set to 1)
77 return bool(os.getenv("VERBOSE", False))
82 # prints debug information (for debug purposes)
85 # args: output data to be printed to the screen (any printable objects)
90 print("[DEBUG]", end=' ')
91 for a in args: print(a, end=' ')
96 # function: error_exit
98 # prints error information and exits with non-zero status
101 # msg: error message (string)
105 print("%s: %s" % (get_tool_name(), msg))
110 # function: get_commands
112 # get supported commands
114 # return value is a dictionary:
115 # { cmd : ( help, opt_map ) }
117 # - cmd is a command (string)
118 # - help is a command's general help information on a command (string)
119 # - opt_map is a disctionary of allowed command's options:
120 # { opt: opt_help }, where
121 # - opt is option ("a:" if option requires a parameter or just "a" elsewhere, where
123 # - opt_help is a help information on the option (string).
125 # See also: add_command
129 if not globals().get("__commands__"): globals()["__commands__"] = {}
130 return globals().get("__commands__")
133 # function: add_command
135 # adds command to the tool
138 # cmd: command (string)
139 # help_string: help information on the command (string)
140 # options: options supported by the command (map {opt: opt_help}, see get_commands)
143 def add_command(cmd, help_string, options):
144 if cmd in get_commands():
145 debug("command %s already exists" % cmd)
147 get_commands()[ cmd ] = ( help_string, options )
153 # gets supported options for the command
156 # cmd: command (string)
158 # return value is map {opt: opt_help} (see get_commands)
162 if cmd is None: cmd = ""
165 opts = get_commands()[cmd][1]
173 # gets help string for the command
176 # cmd: command (string)
178 # return value is unformatted string
182 if cmd is None: cmd = ""
185 help_string = get_commands()[cmd][0]
191 # function: format_string
193 # formats string into block according to the given block width
197 # width: requested width of resulting string block
199 # return formatted string block
202 def format_string(s, width):
203 blocks = s.split("\n")
206 block = block.replace("\t", " [[TAB]] ")
207 words = block.split()
210 word = word if word != "[[TAB]]" else " "*2
211 if not current_string:
212 current_string = word
214 if len(current_string + " " + word) <= width:
215 current_string += " " + word
217 result.append(current_string)
218 current_string = word
222 result.append(current_string)
224 return "\n".join(result)
227 # function: format_commands
229 # formats short help on all supported commands
231 # return string that contains formatted help on commands
234 def format_commands():
235 commands = get_commands()
236 # filter out tool itself
237 commands = sorted([a for a in commands if a != ""])
238 # sort commands alphabetically
239 # get max command's length
240 max_length = max([ len(i) for i in commands])
241 # generate formatting string
244 fmt_string = prefix + "%" + "-%ds" % max_length + separator
246 _h = get_help(_c).split("\n")[0]
247 _h = format_string(_h, 80-(max_length+len(prefix)+len(separator)))
249 _t = prefix + " " * max_length + separator
252 for _i in _h[1:]: _r.append(_t + _i)
254 return "\n".join([ fmt_string % i + _format(i) for i in commands])
257 # function: format_list_options
259 # formats short help on commands's options to be inserted into short help info on a command
262 # cmd: command (string)
264 # return string that contains formatted help
267 def format_list_options(cmd):
269 return "".join([" [%s]" % i for i in opts])
272 # function: format_options
274 # formats help on commands's options to be inserted after short help info on a command
277 # cmd: command (string)
279 # return string that contains formatted help
282 def format_options(cmd):
284 opts_names = list(opts.keys())
285 if not opts: return ""
286 # get max command's length
287 max_length = max([ len(i) for i in opts_names])
288 # generate formatting string
291 fmt_string = prefix + "%" + "-%ds" % max_length + separator
294 _h = format_string(_h, 80-(max_length+len(prefix)+len(separator)))
296 _t = prefix + " " * max_length + separator
299 for _i in _h[1:]: _r.append(_t + _i)
301 return "\n" + "\n".join([ fmt_string % i + _format(i) for i in opts_names]) + "\n"
306 # prints help and exits with zero status
309 # cmd: command (string)
313 if cmd is None: cmd = ""
314 if cmd not in get_commands(): error_exit("unknown command: %s" % cmd)
316 fmt[ "prg" ] = get_tool_name()
317 fmt[ "cmd" ] = get_tool_name() if not cmd else cmd
318 fmt[ "cmd_list" ] = format_commands()
319 fmt[ "opts" ] = format_list_options(cmd)
320 fmt[ "opt_list" ] = format_options(cmd)
321 help_string = get_help(cmd)
322 help_string = help_string.replace("\t", " ")
323 print(help_string.format(**fmt))
328 # function: parse_cmd_line
330 # parses command line; prints error and exits if unsupported command
331 # or option is specified
334 # args: arguments being parsed (list of strings)
337 def parse_cmd_line(args):
338 tool_opts, cmd, cmd_opts, tgt, params = {}, None, {}, None, []
340 commands = get_commands()
345 error_exit("empty option is not allowed")
346 if a.startswith("-"):
347 allowed_opts = list(get_opts(cmd).keys())
348 allowed_opts = dict([(i[1], len(i)>2) for i in allowed_opts])
349 processed_opts = tool_opts if cmd is None else cmd_opts
350 cmd_descr = "" if cmd is None else " for command '%s'" % cmd
356 # "--" format is not supported
357 error_exit("invalid format of option")
358 elif o in allowed_opts:
360 # supported option; requires parameter
362 processed_opts[ o ] = opts
364 elif args and not args[-1].startswith("-") and not args[-1] in commands:
365 processed_opts[ o ] = args.pop()
367 error_exit("option %s%s requires argument" % (o, cmd_descr))
369 # supported option; does not require parameter
370 processed_opts[ o ] = None
375 error_exit("unknown option: -%s%s" % (o, cmd_descr))
380 if cmd not in commands:
381 # unsupported command
382 error_exit("unknown command: %s" % cmd)
389 return tool_opts, cmd, cmd_opts, tgt, params
394 # main entry point of the tool
401 help_string = "Command line tool to manage SALOME configuration.\n\n"
402 help_string += "Usage: {prg}{opts} COMMAND [ARGS]\n"
403 help_string += "{opt_list}\n"
404 help_string += "Supported commands:\n"
405 help_string += "{cmd_list}\n\n"
406 help_string += "See '{prg} help COMMAND' for more information on a specific command."
408 options [ "-s FILE" ] = "Path to the configuration file; default: %s." % defaultConfFile()
414 help_string = "Display help information about this tool.\n\n"
415 help_string += "Usage: {prg} {cmd}{opts}\n"
416 help_string += "{opt_list}"
422 help_string = "Create or modify configuration object.\n\n"
423 help_string += "Usage: {prg} {cmd}{opts} TARGET [PARAM VALUE ...]\n\n"
424 help_string += "\tTARGET: configuration object being created / modified\n"
425 help_string += "\tPARAM: parameter of the target object\n"
426 help_string += "\tVALUE: value to be assigned to the attribute of the target object\n"
427 help_string += "{opt_list}"
433 help_string = "Get parameter of configuration object.\n\n"
434 help_string += "Usage: {prg} {cmd}{opts} TARGET PARAM\n\n"
435 help_string += "\tTARGET: configuration object being inspected\n"
436 help_string += "\tPARAM: parameter of the target object\n"
437 help_string += "{opt_list}"
443 help_string = "Remove configuration object or its attribute(s).\n\n"
444 help_string += "Usage: {prg} {cmd}{opts} TARGET [PARAM ...]\n\n"
445 help_string += "\tTARGET: configuration object\n"
446 help_string += "\tPARAM: attribute of the target object\n"
447 help_string += "{opt_list}"
448 add_command("remove",
453 help_string = "Dump configuration.\n\n"
454 help_string += "Usage: {prg} {cmd}{opts} [TARGET]\n\n"
455 help_string += "\tTARGET: optional configuration object (if not specified,\n"
456 help_string += "\t all configuration is dumped).\n"
457 help_string += "{opt_list}"
463 help_string = "Verify configuration.\n\n"
464 help_string += "Usage: {prg} {cmd}{opts} [TARGET]\n\n"
465 help_string += "\tTARGET: optional configuration object (if not specified,\n"
466 help_string += "\t all configuration is verified).\n"
467 help_string += "{opt_list}"
468 add_command("verify",
473 help_string = "Clean configuration.\n\n"
474 help_string += "Usage: {prg} {cmd}{opts}\n"
475 help_string += "{opt_list}"
482 opts, cmd, cmd_opts, tgt, params = parse_cmd_line(sys.argv[1:])
483 debug("parse command line: options =", opts, "; command =", cmd, "; command options =", cmd_opts, "; target =", tgt, "; arguments =", params)
487 if not cmd or cmd == "help":
493 # check if custom source file is specified
495 src_file = opts["s"] if "s" in opts else None
496 debug("source file:", src_file)
500 cfg_tool = CfgTool(src_file)
501 debug("cfg tool:", cfg_tool)
503 # TODO: we should not check commands in a switch-like block below;
504 # instead processing should be done automatically basing on the
505 # predefined set of commands (TODO)
509 error_exit("%s: target is not specified!" % cmd)
511 error_exit("%s: parameter of target %s is not specified!" % (cmd, tgt))
512 # only one parameter can be read!
514 error_exit("%s: can't read more than one parameter at once!" % cmd)
516 if v is None: return ""
517 elif isinstance(v, list): return " ".join(v)
519 print(_toString(cfg_tool.get(tgt, params[0])))
523 error_exit("%s: target is not specified!" % cmd)
524 # empty parameters list is allowed!
525 cfg_tool.set(tgt, *params)
527 elif cmd == "remove":
529 error_exit("%s: target is not specified!" % cmd)
530 # empty parameters list is allowed!
531 cfg_tool.remove(tgt, *params)
535 error_exit("%s: command does not support parameters!" % cmd)
536 # empty target is allowed!
539 elif cmd == "verify":
541 error_exit("%s: command does not support parameters!" % cmd)
542 # empty target is allowed!
544 if cfg_tool.verify(tgt, errors):
545 msg = " (no products found)" if not cfg_tool.get("cfg", "products") else ""
546 print("Configuration is valid%s." % msg)
548 raise Exception("Configuration is invalid:\n"+ "\n".join(errors))
552 error_exit("%s: command does not support target!" % cmd)
554 error_exit("%s: command does not support parameters!" % cmd)
555 # empty target is allowed!
559 # unknown command: normally we should not go here
560 error_exit("unknown command: %s" % cmd)
563 except Exception as e:
567 if __name__ == "__main__":