Skip to content

Commit a1d7cfb

Browse files
committed
cli: Convert to explicit positional args
There might be some subtle semantic CLI differences here, but it covers more argparse validation cases and simplifies the code, so let's give it a spin
1 parent 05e0460 commit a1d7cfb

2 files changed

Lines changed: 35 additions & 39 deletions

File tree

bugzilla/_cli.py

Lines changed: 25 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ def _setup_action_modify_parser(subparsers):
365365
_parser_add_bz_fields(p, "modify")
366366

367367
g = p.add_argument_group("'modify' specific options")
368+
g.add_argument("ids", nargs="+", help="Bug IDs to modify")
368369
g.add_argument('-k', '--close', metavar="RESOLUTION",
369370
help='Close with the given resolution (WONTFIX, NOTABUG, etc.)')
370371
g.add_argument('-d', '--dupeid', metavar="ORIGINAL",
@@ -385,6 +386,7 @@ def _setup_action_attach_parser(subparsers):
385386
description = "Attach files or download attachments."
386387
p = subparsers.add_parser("attach", description=description, usage=usage)
387388

389+
p.add_argument("ids", nargs="*", help="BUGID references")
388390
p.add_argument('-f', '--file', metavar="FILENAME",
389391
help='File to attach, or filename for data provided on stdin')
390392
p.add_argument('-d', '--description', '--summary',
@@ -401,7 +403,11 @@ def _setup_action_attach_parser(subparsers):
401403
def _setup_action_login_parser(subparsers):
402404
usage = 'bugzilla login [username [password]]'
403405
description = "Log into bugzilla and save a login cookie or token."
404-
subparsers.add_parser("login", description=description, usage=usage)
406+
p = subparsers.add_parser("login", description=description, usage=usage)
407+
p.add_argument("pos_username", nargs="?", help="Optional username",
408+
metavar="username")
409+
p.add_argument("pos_password", nargs="?", help="Optional password",
410+
metavar="password")
405411

406412

407413
def setup_parser():
@@ -784,8 +790,8 @@ def parse_multi(val):
784790
return [b]
785791

786792

787-
def _do_modify(bz, parser, opt, args):
788-
bugid_list = [bugid for a in args for bugid in a.split(',')]
793+
def _do_modify(bz, parser, opt):
794+
bugid_list = [bugid for a in opt.ids for bugid in a.split(',')]
789795

790796
add_wb, rm_wb, set_wb = _parse_triset(opt.whiteboard)
791797
add_devwb, rm_devwb, set_devwb = _parse_triset(opt.devel_whiteboard)
@@ -917,11 +923,7 @@ def _do_modify(bz, parser, opt, args):
917923
bz.build_update(**{wb: " ".join(newval)}))
918924

919925

920-
def _do_get_attach(bz, opt, parser, args):
921-
if args:
922-
parser.error("Extra args '%s' not used for getting attachments" %
923-
args)
924-
926+
def _do_get_attach(bz, opt):
925927
for bug in bz.getbugs(opt.getall):
926928
opt.get += bug.get_attachment_ids()
927929

@@ -937,8 +939,8 @@ def _do_get_attach(bz, opt, parser, args):
937939
return
938940

939941

940-
def _do_set_attach(bz, opt, parser, args):
941-
if not args:
942+
def _do_set_attach(bz, opt, parser):
943+
if not opt.ids:
942944
parser.error("Bug ID must be specified for setting attachments")
943945

944946
if sys.stdin.isatty():
@@ -969,7 +971,7 @@ def _do_set_attach(bz, opt, parser, args):
969971
desc = opt.desc or os.path.basename(fileobj.name)
970972

971973
# Upload attachments
972-
for bugid in args:
974+
for bugid in opt.ids:
973975
attid = bz.attachfile(bugid, fileobj, desc, **kwargs)
974976
print("Created attachment %i on bug %s" % (attid, bugid))
975977

@@ -999,29 +1001,22 @@ def _make_bz_instance(opt):
9991001
return bz
10001002

10011003

1002-
def _handle_login(opt, parser, args, action, bz):
1004+
def _handle_login(opt, action, bz):
10031005
"""
10041006
Handle all login related bits
10051007
"""
10061008
is_login_command = (action == 'login')
10071009

10081010
do_interactive_login = (is_login_command or
10091011
opt.login or opt.username or opt.password)
1010-
1011-
if is_login_command:
1012-
if len(args) == 2:
1013-
(opt.username, opt.password) = args
1014-
elif len(args) == 1:
1015-
(opt.username, ) = args
1016-
elif len(args) > 2:
1017-
parser.error("Too many arguments for login")
1012+
username = getattr(opt, "pos_username", None) or opt.username
1013+
password = getattr(opt, "pos_password", None) or opt.password
10181014

10191015
try:
10201016
if do_interactive_login:
10211017
if bz.url:
10221018
print("Logging into %s" % urlparse(bz.url)[1])
1023-
bz.interactive_login(
1024-
opt.username, opt.password)
1019+
bz.interactive_login(username, password)
10251020
except bugzilla.BugzillaError as e:
10261021
print(str(e))
10271022
sys.exit(1)
@@ -1042,7 +1037,7 @@ def _handle_login(opt, parser, args, action, bz):
10421037

10431038
def _main(unittest_bz_instance):
10441039
parser = setup_parser()
1045-
opt, args = parser.parse_known_args()
1040+
opt = parser.parse_args()
10461041
action = opt.command
10471042
setup_logging(opt.debug, opt.verbose)
10481043

@@ -1057,7 +1052,7 @@ def _main(unittest_bz_instance):
10571052
bz = _make_bz_instance(opt)
10581053

10591054
# Handle login options
1060-
_handle_login(opt, parser, args, action, bz)
1055+
_handle_login(opt, action, bz)
10611056

10621057

10631058
###########################
@@ -1070,9 +1065,6 @@ def _main(unittest_bz_instance):
10701065

10711066
buglist = []
10721067
if action == 'info':
1073-
if args:
1074-
parser.error("Extra arguments '%s'" % args)
1075-
10761068
if not (opt.products or
10771069
opt.components or
10781070
opt.component_owners or
@@ -1082,32 +1074,26 @@ def _main(unittest_bz_instance):
10821074
_do_info(bz, opt)
10831075

10841076
elif action == 'query':
1085-
if args:
1086-
parser.error("Extra arguments '%s'" % args)
1087-
10881077
buglist = _do_query(bz, opt, parser)
10891078
if opt.test_return_result:
10901079
return buglist
10911080

10921081
elif action == 'new':
1093-
if args:
1094-
parser.error("Extra arguments '%s'" % args)
10951082
buglist = _do_new(bz, opt, parser)
10961083
if opt.test_return_result:
10971084
return buglist
10981085

10991086
elif action == 'attach':
11001087
if opt.get or opt.getall:
1101-
_do_get_attach(bz, opt, parser, args)
1088+
if opt.ids:
1089+
parser.error("Bug IDs '%s' not used for "
1090+
"getting attachments" % opt.ids)
1091+
_do_get_attach(bz, opt)
11021092
else:
1103-
_do_set_attach(bz, opt, parser, args)
1093+
_do_set_attach(bz, opt, parser)
11041094

11051095
elif action == 'modify':
1106-
if not args:
1107-
parser.error('No bug IDs given '
1108-
'(maybe you forgot an argument somewhere?)')
1109-
1110-
modout = _do_modify(bz, parser, opt, args)
1096+
modout = _do_modify(bz, parser, opt)
11111097
if opt.test_return_result:
11121098
return modout
11131099
else:

tests/misc.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ def testVersion(self):
3838
out = tests.clicomm("bugzilla --version", None)
3939
self.assertTrue(len(out.splitlines()) >= 2)
4040

41+
def testPositionalArgs(self):
42+
# Make sure cli correctly rejects ambiguous positional args
43+
out = tests.clicomm("bugzilla login --xbadarg foo",
44+
None, expectfail=True)
45+
self.assertTrue("unrecognized arguments: --xbadarg" in out)
46+
47+
out = tests.clicomm("bugzilla modify 123456 --foobar --status NEW",
48+
None, expectfail=True)
49+
self.assertTrue("unrecognized arguments: --foobar" in out)
50+
4151

4252
class MiscAPI(unittest.TestCase):
4353
"""

0 commit comments

Comments
 (0)