Revision d971402f
b/lib/cli.py | ||
---|---|---|
1480 | 1480 |
] |
1481 | 1481 |
|
1482 | 1482 |
|
1483 |
def _ParseArgs(argv, commands, aliases, env_override): |
|
1483 |
class _ShowUsage(Exception): |
|
1484 |
"""Exception class for L{_ParseArgs}. |
|
1485 |
|
|
1486 |
""" |
|
1487 |
def __init__(self, exit_error): |
|
1488 |
"""Initializes instances of this class. |
|
1489 |
|
|
1490 |
@type exit_error: bool |
|
1491 |
@param exit_error: Whether to report failure on exit |
|
1492 |
|
|
1493 |
""" |
|
1494 |
Exception.__init__(self) |
|
1495 |
self.exit_error = exit_error |
|
1496 |
|
|
1497 |
|
|
1498 |
class _ShowVersion(Exception): |
|
1499 |
"""Exception class for L{_ParseArgs}. |
|
1500 |
|
|
1501 |
""" |
|
1502 |
|
|
1503 |
|
|
1504 |
def _ParseArgs(binary, argv, commands, aliases, env_override): |
|
1484 | 1505 |
"""Parser for the command line arguments. |
1485 | 1506 |
|
1486 | 1507 |
This function parses the arguments and returns the function which |
1487 | 1508 |
must be executed together with its (modified) arguments. |
1488 | 1509 |
|
1489 |
@param argv: the command line
|
|
1490 |
@param commands: dictionary with special contents, see the design
|
|
1491 |
doc for cmdline handling
|
|
1492 |
@param aliases: dictionary with command aliases {'alias': 'target, ...}
|
|
1510 |
@param binary: Script name
|
|
1511 |
@param argv: Command line arguments
|
|
1512 |
@param commands: Dictionary containing command definitions
|
|
1513 |
@param aliases: dictionary with command aliases {"alias": "target", ...}
|
|
1493 | 1514 |
@param env_override: list of env variables allowed for default args |
1515 |
@raise _ShowUsage: If usage description should be shown |
|
1516 |
@raise _ShowVersion: If version should be shown |
|
1494 | 1517 |
|
1495 | 1518 |
""" |
1496 | 1519 |
assert not (env_override - set(commands)) |
1520 |
assert not (set(aliases.keys()) & set(commands.keys())) |
|
1497 | 1521 |
|
1498 |
if len(argv) == 0:
|
|
1499 |
binary = "<command>"
|
|
1522 |
if len(argv) > 1:
|
|
1523 |
cmd = argv[1]
|
|
1500 | 1524 |
else: |
1501 |
binary = argv[0].split("/")[-1] |
|
1525 |
# No option or command given |
|
1526 |
raise _ShowUsage(exit_error=True) |
|
1502 | 1527 |
|
1503 |
if len(argv) > 1 and argv[1] == "--version": |
|
1504 |
ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION, |
|
1505 |
constants.RELEASE_VERSION) |
|
1506 |
# Quit right away. That way we don't have to care about this special |
|
1507 |
# argument. optparse.py does it the same. |
|
1508 |
sys.exit(0) |
|
1509 |
|
|
1510 |
if len(argv) < 2 or not (argv[1] in commands or |
|
1511 |
argv[1] in aliases): |
|
1512 |
# let's do a nice thing |
|
1513 |
sortedcmds = commands.keys() |
|
1514 |
sortedcmds.sort() |
|
1515 |
|
|
1516 |
ToStdout("Usage: %s {command} [options...] [argument...]", binary) |
|
1517 |
ToStdout("%s <command> --help to see details, or man %s", binary, binary) |
|
1518 |
ToStdout("") |
|
1519 |
|
|
1520 |
# compute the max line length for cmd + usage |
|
1521 |
mlen = max([len(" %s" % cmd) for cmd in commands]) |
|
1522 |
mlen = min(60, mlen) # should not get here... |
|
1523 |
|
|
1524 |
# and format a nice command list |
|
1525 |
ToStdout("Commands:") |
|
1526 |
for cmd in sortedcmds: |
|
1527 |
cmdstr = " %s" % (cmd,) |
|
1528 |
help_text = commands[cmd][4] |
|
1529 |
help_lines = textwrap.wrap(help_text, 79 - 3 - mlen) |
|
1530 |
ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0)) |
|
1531 |
for line in help_lines: |
|
1532 |
ToStdout("%-*s %s", mlen, "", line) |
|
1533 |
|
|
1534 |
ToStdout("") |
|
1535 |
|
|
1536 |
return None, None, None |
|
1528 |
if cmd == "--version": |
|
1529 |
raise _ShowVersion() |
|
1530 |
elif cmd == "--help": |
|
1531 |
raise _ShowUsage(exit_error=False) |
|
1532 |
elif not (cmd in commands or cmd in aliases): |
|
1533 |
raise _ShowUsage(exit_error=True) |
|
1537 | 1534 |
|
1538 | 1535 |
# get command, unalias it, and look it up in commands |
1539 |
cmd = argv.pop(1) |
|
1540 | 1536 |
if cmd in aliases: |
1541 |
if cmd in commands: |
|
1542 |
raise errors.ProgrammerError("Alias '%s' overrides an existing" |
|
1543 |
" command" % cmd) |
|
1544 |
|
|
1545 | 1537 |
if aliases[cmd] not in commands: |
1546 | 1538 |
raise errors.ProgrammerError("Alias '%s' maps to non-existing" |
1547 | 1539 |
" command '%s'" % (cmd, aliases[cmd])) |
... | ... | |
1552 | 1544 |
args_env_name = ("%s_%s" % (binary.replace("-", "_"), cmd)).upper() |
1553 | 1545 |
env_args = os.environ.get(args_env_name) |
1554 | 1546 |
if env_args: |
1555 |
argv = utils.InsertAtPos(argv, 1, shlex.split(env_args))
|
|
1547 |
argv = utils.InsertAtPos(argv, 2, shlex.split(env_args))
|
|
1556 | 1548 |
|
1557 | 1549 |
func, args_def, parser_opts, usage, description = commands[cmd] |
1558 | 1550 |
parser = OptionParser(option_list=parser_opts + COMMON_OPTS, |
... | ... | |
1560 | 1552 |
formatter=TitledHelpFormatter(), |
1561 | 1553 |
usage="%%prog %s %s" % (cmd, usage)) |
1562 | 1554 |
parser.disable_interspersed_args() |
1563 |
options, args = parser.parse_args(args=argv[1:])
|
|
1555 |
options, args = parser.parse_args(args=argv[2:])
|
|
1564 | 1556 |
|
1565 | 1557 |
if not _CheckArguments(cmd, args_def, args): |
1566 | 1558 |
return None, None, None |
... | ... | |
1568 | 1560 |
return func, options, args |
1569 | 1561 |
|
1570 | 1562 |
|
1563 |
def _FormatUsage(binary, commands): |
|
1564 |
"""Generates a nice description of all commands. |
|
1565 |
|
|
1566 |
@param binary: Script name |
|
1567 |
@param commands: Dictionary containing command definitions |
|
1568 |
|
|
1569 |
""" |
|
1570 |
# compute the max line length for cmd + usage |
|
1571 |
mlen = min(60, max(map(len, commands))) |
|
1572 |
|
|
1573 |
yield "Usage: %s {command} [options...] [argument...]" % binary |
|
1574 |
yield "%s <command> --help to see details, or man %s" % (binary, binary) |
|
1575 |
yield "" |
|
1576 |
yield "Commands:" |
|
1577 |
|
|
1578 |
# and format a nice command list |
|
1579 |
for (cmd, (_, _, _, _, help_text)) in sorted(commands.items()): |
|
1580 |
help_lines = textwrap.wrap(help_text, 79 - 3 - mlen) |
|
1581 |
yield " %-*s - %s" % (mlen, cmd, help_lines.pop(0)) |
|
1582 |
for line in help_lines: |
|
1583 |
yield " %-*s %s" % (mlen, "", line) |
|
1584 |
|
|
1585 |
yield "" |
|
1586 |
|
|
1587 |
|
|
1571 | 1588 |
def _CheckArguments(cmd, args_def, args): |
1572 | 1589 |
"""Verifies the arguments using the argument definition. |
1573 | 1590 |
|
... | ... | |
2242 | 2259 |
aliases = {} |
2243 | 2260 |
|
2244 | 2261 |
try: |
2245 |
func, options, args = _ParseArgs(sys.argv, commands, aliases, env_override) |
|
2262 |
(func, options, args) = _ParseArgs(binary, sys.argv, commands, aliases, |
|
2263 |
env_override) |
|
2264 |
except _ShowVersion: |
|
2265 |
ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION, |
|
2266 |
constants.RELEASE_VERSION) |
|
2267 |
return constants.EXIT_SUCCESS |
|
2268 |
except _ShowUsage, err: |
|
2269 |
for line in _FormatUsage(binary, commands): |
|
2270 |
ToStdout(line) |
|
2271 |
|
|
2272 |
if err.exit_error: |
|
2273 |
return constants.EXIT_FAILURE |
|
2274 |
else: |
|
2275 |
return constants.EXIT_SUCCESS |
|
2246 | 2276 |
except errors.ParameterError, err: |
2247 | 2277 |
result, err_msg = FormatError(err) |
2248 | 2278 |
ToStderr(err_msg) |
b/test/ganeti.cli_unittest.py | ||
---|---|---|
922 | 922 |
self.assertEqual(cli.FormatTimestamp(i), "?") |
923 | 923 |
|
924 | 924 |
|
925 |
if __name__ == '__main__': |
|
925 |
class TestFormatUsage(unittest.TestCase): |
|
926 |
def test(self): |
|
927 |
binary = "gnt-unittest" |
|
928 |
commands = { |
|
929 |
"cmdA": |
|
930 |
(NotImplemented, NotImplemented, NotImplemented, NotImplemented, |
|
931 |
"description of A"), |
|
932 |
"bbb": |
|
933 |
(NotImplemented, NotImplemented, NotImplemented, NotImplemented, |
|
934 |
"Hello World," * 10), |
|
935 |
"longname": |
|
936 |
(NotImplemented, NotImplemented, NotImplemented, NotImplemented, |
|
937 |
"Another description"), |
|
938 |
} |
|
939 |
|
|
940 |
self.assertEqual(list(cli._FormatUsage(binary, commands)), [ |
|
941 |
"Usage: gnt-unittest {command} [options...] [argument...]", |
|
942 |
"gnt-unittest <command> --help to see details, or man gnt-unittest", |
|
943 |
"", |
|
944 |
"Commands:", |
|
945 |
(" bbb - Hello World,Hello World,Hello World,Hello World,Hello" |
|
946 |
" World,Hello"), |
|
947 |
" World,Hello World,Hello World,Hello World,Hello World,", |
|
948 |
" cmdA - description of A", |
|
949 |
" longname - Another description", |
|
950 |
"", |
|
951 |
]) |
|
952 |
|
|
953 |
|
|
954 |
class TestParseArgs(unittest.TestCase): |
|
955 |
def testNoArguments(self): |
|
956 |
for argv in [[], ["gnt-unittest"]]: |
|
957 |
try: |
|
958 |
cli._ParseArgs("gnt-unittest", argv, {}, {}, set()) |
|
959 |
except cli._ShowUsage, err: |
|
960 |
self.assertTrue(err.exit_error) |
|
961 |
else: |
|
962 |
self.fail("Did not raise exception") |
|
963 |
|
|
964 |
def testVersion(self): |
|
965 |
for argv in [["test", "--version"], ["test", "--version", "somethingelse"]]: |
|
966 |
try: |
|
967 |
cli._ParseArgs("test", argv, {}, {}, set()) |
|
968 |
except cli._ShowVersion: |
|
969 |
pass |
|
970 |
else: |
|
971 |
self.fail("Did not raise exception") |
|
972 |
|
|
973 |
def testHelp(self): |
|
974 |
for argv in [["test", "--help"], ["test", "--help", "somethingelse"]]: |
|
975 |
try: |
|
976 |
cli._ParseArgs("test", argv, {}, {}, set()) |
|
977 |
except cli._ShowUsage, err: |
|
978 |
self.assertFalse(err.exit_error) |
|
979 |
else: |
|
980 |
self.fail("Did not raise exception") |
|
981 |
|
|
982 |
def testUnknownCommandOrAlias(self): |
|
983 |
for argv in [["test", "list"], ["test", "somethingelse", "--help"]]: |
|
984 |
try: |
|
985 |
cli._ParseArgs("test", argv, {}, {}, set()) |
|
986 |
except cli._ShowUsage, err: |
|
987 |
self.assertTrue(err.exit_error) |
|
988 |
else: |
|
989 |
self.fail("Did not raise exception") |
|
990 |
|
|
991 |
def testInvalidAliasList(self): |
|
992 |
cmd = { |
|
993 |
"list": NotImplemented, |
|
994 |
"foo": NotImplemented, |
|
995 |
} |
|
996 |
aliases = { |
|
997 |
"list": NotImplemented, |
|
998 |
"foo": NotImplemented, |
|
999 |
} |
|
1000 |
assert sorted(cmd.keys()) == sorted(aliases.keys()) |
|
1001 |
self.assertRaises(AssertionError, cli._ParseArgs, "test", |
|
1002 |
["test", "list"], cmd, aliases, set()) |
|
1003 |
|
|
1004 |
def testAliasForNonExistantCommand(self): |
|
1005 |
cmd = {} |
|
1006 |
aliases = { |
|
1007 |
"list": NotImplemented, |
|
1008 |
} |
|
1009 |
self.assertRaises(errors.ProgrammerError, cli._ParseArgs, "test", |
|
1010 |
["test", "list"], cmd, aliases, set()) |
|
1011 |
|
|
1012 |
|
|
1013 |
if __name__ == "__main__": |
|
926 | 1014 |
testutils.GanetiTestProgram() |
b/test/gnt-cli.test | ||
---|---|---|
2 | 2 |
$SCRIPTS/gnt-node --help |
3 | 3 |
>>>/Usage:/ |
4 | 4 |
>>>2 |
5 |
>>>= 0 |
|
6 |
$SCRIPTS/gnt-node UNKNOWN |
|
7 |
>>>/Usage:/ |
|
8 |
>>>2 |
|
5 | 9 |
>>>= 1 |
6 | 10 |
$SCRIPTS/gnt-node --version |
7 | 11 |
>>>/^gnt-/ |
... | ... | |
11 | 15 |
$SCRIPTS/gnt-instance --help |
12 | 16 |
>>>/Usage:/ |
13 | 17 |
>>>2 |
18 |
>>>= 0 |
|
19 |
$SCRIPTS/gnt-instance UNKNOWN |
|
20 |
>>>/Usage:/ |
|
21 |
>>>2 |
|
14 | 22 |
>>>= 1 |
15 | 23 |
$SCRIPTS/gnt-instance --version |
16 | 24 |
>>>/^gnt-instance/ |
... | ... | |
20 | 28 |
$SCRIPTS/gnt-os --help |
21 | 29 |
>>>/Usage:/ |
22 | 30 |
>>>2 |
31 |
>>>= 0 |
|
32 |
$SCRIPTS/gnt-os UNKNOWN |
|
33 |
>>>/Usage:/ |
|
34 |
>>>2 |
|
23 | 35 |
>>>= 1 |
24 | 36 |
$SCRIPTS/gnt-os --version |
25 | 37 |
>>>/^gnt-/ |
... | ... | |
29 | 41 |
$SCRIPTS/gnt-group --help |
30 | 42 |
>>>/Usage:/ |
31 | 43 |
>>>2 |
44 |
>>>= 0 |
|
45 |
$SCRIPTS/gnt-group UNKNOWN |
|
46 |
>>>/Usage:/ |
|
47 |
>>>2 |
|
32 | 48 |
>>>= 1 |
33 | 49 |
$SCRIPTS/gnt-group --version |
34 | 50 |
>>>/^gnt-/ |
... | ... | |
38 | 54 |
$SCRIPTS/gnt-job --help |
39 | 55 |
>>>/Usage:/ |
40 | 56 |
>>>2 |
57 |
>>>= 0 |
|
58 |
$SCRIPTS/gnt-job UNKNOWN |
|
59 |
>>>/Usage:/ |
|
60 |
>>>2 |
|
41 | 61 |
>>>= 1 |
42 | 62 |
$SCRIPTS/gnt-job --version |
43 | 63 |
>>>/^gnt-/ |
... | ... | |
47 | 67 |
$SCRIPTS/gnt-cluster --help |
48 | 68 |
>>>/Usage:/ |
49 | 69 |
>>>2 |
70 |
>>>= 0 |
|
71 |
$SCRIPTS/gnt-cluster UNKNOWN |
|
72 |
>>>/Usage:/ |
|
73 |
>>>2 |
|
50 | 74 |
>>>= 1 |
51 | 75 |
$SCRIPTS/gnt-cluster --version |
52 | 76 |
>>>/^gnt-/ |
... | ... | |
56 | 80 |
$SCRIPTS/gnt-backup --help |
57 | 81 |
>>>/Usage:/ |
58 | 82 |
>>>2 |
83 |
>>>= 0 |
|
84 |
$SCRIPTS/gnt-backup UNKNOWN |
|
85 |
>>>/Usage:/ |
|
86 |
>>>2 |
|
59 | 87 |
>>>= 1 |
60 | 88 |
$SCRIPTS/gnt-backup --version |
61 | 89 |
>>>/^gnt-/ |
... | ... | |
65 | 93 |
$SCRIPTS/gnt-debug --help |
66 | 94 |
>>>/Usage:/ |
67 | 95 |
>>>2 |
96 |
>>>= 0 |
|
97 |
$SCRIPTS/gnt-debug UNKNOWN |
|
98 |
>>>/Usage:/ |
|
99 |
>>>2 |
|
68 | 100 |
>>>= 1 |
69 | 101 |
$SCRIPTS/gnt-debug --version |
70 | 102 |
>>>/^gnt-/ |
Also available in: Unified diff