From 2428bbaec08609a1751cc7a35c2052bb89be9344 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 17 Mar 2024 21:01:55 +0200 Subject: [PATCH 01/15] Fix whitespace --- Lib/webbrowser.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 0424c53b7ccaf9..8c1ec4cd5a3b30 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -11,14 +11,17 @@ __all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"] + class Error(Exception): pass + _lock = threading.RLock() _browsers = {} # Dictionary of available browser controllers _tryorder = None # Preference order of available browsers _os_preferred_browser = None # The preferred browser + def register(name, klass, instance=None, *, preferred=False): """Register a browser connector.""" with _lock: @@ -34,6 +37,7 @@ def register(name, klass, instance=None, *, preferred=False): else: _tryorder.append(name) + def get(using=None): """Return a browser launcher instance appropriate for the environment.""" if _tryorder is None: @@ -64,6 +68,7 @@ def get(using=None): return command[0]() raise Error("could not locate runnable browser") + # Please note: the following definition hides a builtin function. # It is recommended one does "import webbrowser" and uses webbrowser.open(url) # instead of "from webbrowser import *". @@ -87,6 +92,7 @@ def open(url, new=0, autoraise=True): return True return False + def open_new(url): """Open url in a new window of the default browser. @@ -94,6 +100,7 @@ def open_new(url): """ return open(url, 1) + def open_new_tab(url): """Open url in a new page ("tab") of the default browser. @@ -225,7 +232,8 @@ def _invoke(self, args, remote, autoraise, url=None): # use autoraise argument only for remote invocation autoraise = int(autoraise) opt = self.raise_opts[autoraise] - if opt: raise_opt = [opt] + if opt: + raise_opt = [opt] cmdline = [self.name] + raise_opt + args @@ -310,6 +318,7 @@ class Chrome(UnixBrowser): remote_action_newtab = "" background = True + Chromium = Chrome @@ -461,7 +470,6 @@ def register_X_browsers(): if shutil.which("opera"): register("opera", None, Opera("opera")) - if shutil.which("microsoft-edge"): register("microsoft-edge", None, Edge("microsoft-edge")) @@ -511,7 +519,7 @@ def register_standard_browsers(): cmd = "xdg-settings get default-web-browser".split() raw_result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL) result = raw_result.decode().strip() - except (FileNotFoundError, subprocess.CalledProcessError, PermissionError, NotADirectoryError) : + except (FileNotFoundError, subprocess.CalledProcessError, PermissionError, NotADirectoryError): pass else: global _os_preferred_browser @@ -582,14 +590,14 @@ def __init__(self, name='default'): def open(self, url, new=0, autoraise=True): sys.audit("webbrowser.open", url) if self.name == 'default': - script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser + script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser else: script = f''' tell application "%s" activate open location "%s" end - '''%(self.name, url.replace('"', '%22')) + ''' % (self.name, url.replace('"', '%22')) osapipe = os.popen("osascript", "w") if osapipe is None: @@ -607,15 +615,17 @@ def main(): -t: open new tab -h, --help: show help""" % sys.argv[0] try: - opts, args = getopt.getopt(sys.argv[1:], 'ntdh',['help']) + opts, args = getopt.getopt(sys.argv[1:], 'ntdh', ['help']) except getopt.error as msg: print(msg, file=sys.stderr) print(usage, file=sys.stderr) sys.exit(1) new_win = 0 for o, a in opts: - if o == '-n': new_win = 1 - elif o == '-t': new_win = 2 + if o == '-n': + new_win = 1 + elif o == '-t': + new_win = 2 elif o == '-h' or o == '--help': print(usage, file=sys.stderr) sys.exit() @@ -628,5 +638,6 @@ def main(): print("\a") + if __name__ == "__main__": main() From ba99bcc081650edf1beea875b130925ed01de2e3 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 17 Mar 2024 21:02:36 +0200 Subject: [PATCH 02/15] Triple backticks for docstrings --- Lib/webbrowser.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 8c1ec4cd5a3b30..f5fc9031783193 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -310,7 +310,7 @@ class Epiphany(UnixBrowser): class Chrome(UnixBrowser): - "Launcher class for Google Chrome browser." + """Launcher class for Google Chrome browser.""" remote_args = ['%action', '%s'] remote_action = "" @@ -323,7 +323,7 @@ class Chrome(UnixBrowser): class Opera(UnixBrowser): - "Launcher class for Opera browser." + """Launcher class for Opera browser.""" remote_args = ['%action', '%s'] remote_action = "" @@ -333,7 +333,7 @@ class Opera(UnixBrowser): class Elinks(UnixBrowser): - "Launcher class for Elinks browsers." + """Launcher class for Elinks browsers.""" remote_args = ['-remote', 'openURL(%s%action)'] remote_action = "" @@ -400,7 +400,7 @@ def open(self, url, new=0, autoraise=True): class Edge(UnixBrowser): - "Launcher class for Microsoft Edge browser." + """Launcher class for Microsoft Edge browser.""" remote_args = ['%action', '%s'] remote_action = "" From a1ebffc05d3cefeaa850e67651d5d0ed15c27142 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 17 Mar 2024 21:03:45 +0200 Subject: [PATCH 03/15] Remove redundant parentheses --- Lib/webbrowser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index f5fc9031783193..58f0c924d926f9 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -204,7 +204,7 @@ def open(self, url, new=0, autoraise=True): else: p = subprocess.Popen(cmdline, close_fds=True, start_new_session=True) - return (p.poll() is None) + return p.poll() is None except OSError: return False @@ -396,7 +396,7 @@ def open(self, url, new=0, autoraise=True): except OSError: return False else: - return (p.poll() is None) + return p.poll() is None class Edge(UnixBrowser): From c60dcae5aeb7a7a25b4aad86d3daf8568e792e6a Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 17 Mar 2024 22:45:53 +0200 Subject: [PATCH 04/15] webbrowser: replace getopt with argparse, add long options --- Doc/library/webbrowser.rst | 9 ++++++--- Lib/webbrowser.py | 41 +++++++++++++++----------------------- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/Doc/library/webbrowser.rst b/Doc/library/webbrowser.rst index 4667b81e38ada2..ce24f2a0dd0289 100644 --- a/Doc/library/webbrowser.rst +++ b/Doc/library/webbrowser.rst @@ -35,9 +35,12 @@ browser and wait. The script :program:`webbrowser` can be used as a command-line interface for the module. It accepts a URL as the argument. It accepts the following optional -parameters: ``-n`` opens the URL in a new browser window, if possible; -``-t`` opens the URL in a new browser page ("tab"). The options are, -naturally, mutually exclusive. Usage example:: +parameters: + +* ``-n``/``--new-window`` opens the URL in a new browser window, if possible. +* ``-t``/``--new-tab`` opens the URL in a new browser page ("tab"). + +The options are, naturally, mutually exclusive. Usage example:: python -m webbrowser -t "https://www.python.org" diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 58f0c924d926f9..29dee08a16beb2 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -609,32 +609,23 @@ def open(self, url, new=0, autoraise=True): def main(): - import getopt - usage = """Usage: %s [-n | -t | -h] url - -n: open new window - -t: open new tab - -h, --help: show help""" % sys.argv[0] - try: - opts, args = getopt.getopt(sys.argv[1:], 'ntdh', ['help']) - except getopt.error as msg: - print(msg, file=sys.stderr) - print(usage, file=sys.stderr) - sys.exit(1) + import argparse + parser = argparse.ArgumentParser(description="Open URL in a web browser.") + parser.add_argument("url", help="URL to open") + group = parser.add_mutually_exclusive_group() + group.add_argument("-n", "--new-window", action="store_true", + help="open new window") + group.add_argument("-t", "--new-tab", action="store_true", + help="open new tab") + args = parser.parse_args() + new_win = 0 - for o, a in opts: - if o == '-n': - new_win = 1 - elif o == '-t': - new_win = 2 - elif o == '-h' or o == '--help': - print(usage, file=sys.stderr) - sys.exit() - if len(args) != 1: - print(usage, file=sys.stderr) - sys.exit(1) - - url = args[0] - open(url, new_win) + if args.new_window: + new_win = 1 + elif args.new_tab: + new_win = 2 + + open(args.url, new_win) print("\a") From 9361e0746824fabd42d0ff1a51bb054bfd2f4a8e Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 17 Mar 2024 22:52:04 +0200 Subject: [PATCH 05/15] Fix/use f-strings --- Lib/webbrowser.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 29dee08a16beb2..66f40f8ada29fd 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -274,8 +274,8 @@ def open(self, url, new=0, autoraise=True): else: action = self.remote_action_newtab else: - raise Error("Bad 'new' parameter to open(); " + - "expected 0, 1, or 2, got %s" % new) + raise Error("Bad 'new' parameter to open(); " + f"expected 0, 1, or 2, got {new}") args = [arg.replace("%s", url).replace("%action", action) for arg in self.remote_args] @@ -589,15 +589,16 @@ def __init__(self, name='default'): def open(self, url, new=0, autoraise=True): sys.audit("webbrowser.open", url) + url = url.replace('"', '%22') if self.name == 'default': - script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser + script = f'open location "{url}"' # opens in default browser else: script = f''' - tell application "%s" + tell application "{self.name}" activate - open location "%s" + open location "{url}" end - ''' % (self.name, url.replace('"', '%22')) + ''' osapipe = os.popen("osascript", "w") if osapipe is None: From 2915de73b6d9c9c8c97576e1988434d824f4c26a Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 17 Mar 2024 22:52:20 +0200 Subject: [PATCH 06/15] New-style class --- Lib/webbrowser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 66f40f8ada29fd..21328d9620c618 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -143,7 +143,7 @@ def _synthesize(browser, *, preferred=False): # General parent classes -class BaseBrowser(object): +class BaseBrowser: """Parent class for all browsers. Do not use directly.""" args = ['%s'] From fa90d157ca063a4df7f6598f4b21615510981dd6 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 18 Mar 2024 00:27:56 +0200 Subject: [PATCH 07/15] Refactor CLI for testability --- Lib/webbrowser.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 21328d9620c618..77d8f9406b27f7 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -609,16 +609,24 @@ def open(self, url, new=0, autoraise=True): return not rc -def main(): +def parse_args(arg_list: list[str] | None): import argparse parser = argparse.ArgumentParser(description="Open URL in a web browser.") parser.add_argument("url", help="URL to open") + group = parser.add_mutually_exclusive_group() group.add_argument("-n", "--new-window", action="store_true", help="open new window") group.add_argument("-t", "--new-tab", action="store_true", help="open new tab") - args = parser.parse_args() + + args = parser.parse_args(arg_list) + + return args + + +def main(arg_list: list[str] | None = None): + args = parse_args(arg_list) new_win = 0 if args.new_window: From fac943d37f4d36a399e317da4011df93319ac288 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 18 Mar 2024 00:28:07 +0200 Subject: [PATCH 08/15] Fix whitespace --- Lib/test/test_webbrowser.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py index 8c074cb28a87e3..d6a461ca4ee8d2 100644 --- a/Lib/test/test_webbrowser.py +++ b/Lib/test/test_webbrowser.py @@ -201,7 +201,7 @@ class ELinksCommandTest(CommandTestMixin, unittest.TestCase): def test_open(self): self._test('open', options=['-remote'], - arguments=['openURL({})'.format(URL)]) + arguments=['openURL({})'.format(URL)]) def test_open_with_autoraise_false(self): self._test('open', @@ -271,7 +271,6 @@ def test_register_default(self): def test_register_preferred(self): self._check_registration(preferred=True) - @unittest.skipUnless(sys.platform == "darwin", "macOS specific test") def test_no_xdg_settings_on_macOS(self): # On macOS webbrowser should not use xdg-settings to From 562a760ee01cd4015c8d3f2bc66bab2b4688a652 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 18 Mar 2024 00:29:10 +0200 Subject: [PATCH 09/15] Use f-strings --- Lib/test/test_webbrowser.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py index d6a461ca4ee8d2..96a67b8b4390cd 100644 --- a/Lib/test/test_webbrowser.py +++ b/Lib/test/test_webbrowser.py @@ -201,22 +201,22 @@ class ELinksCommandTest(CommandTestMixin, unittest.TestCase): def test_open(self): self._test('open', options=['-remote'], - arguments=['openURL({})'.format(URL)]) + arguments=[f'openURL({URL})']) def test_open_with_autoraise_false(self): self._test('open', options=['-remote'], - arguments=['openURL({})'.format(URL)]) + arguments=[f'openURL({URL})']) def test_open_new(self): self._test('open_new', options=['-remote'], - arguments=['openURL({},new-window)'.format(URL)]) + arguments=[f'openURL({URL},new-window)']) def test_open_new_tab(self): self._test('open_new_tab', options=['-remote'], - arguments=['openURL({},new-tab)'.format(URL)]) + arguments=[f'openURL({URL},new-tab)']) class BrowserRegistrationTest(unittest.TestCase): From 7ace992e0fe80b5c780c9074d973a004583cfe86 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 18 Mar 2024 00:31:28 +0200 Subject: [PATCH 10/15] Add more tests --- Lib/test/test_webbrowser.py | 77 ++++++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py index 96a67b8b4390cd..8e5517c417580c 100644 --- a/Lib/test/test_webbrowser.py +++ b/Lib/test/test_webbrowser.py @@ -1,12 +1,14 @@ -import webbrowser -import unittest import os -import sys +import re +import shlex import subprocess -from unittest import mock +import sys +import unittest +import webbrowser from test import support from test.support import import_helper from test.support import os_helper +from unittest import mock if not support.has_subprocess_support: raise unittest.SkipTest("test webserver requires subprocess") @@ -94,6 +96,15 @@ def test_open_new_tab(self): options=[], arguments=[URL]) + def test_open_bad_new_parameter(self): + with self.assertRaisesRegex(webbrowser.Error, + re.escape("Bad 'new' parameter to open(); " + "expected 0, 1, or 2, got 999")): + self._test('open', + options=[], + arguments=[URL], + kw=dict(new=999)) + class EdgeCommandTest(CommandTestMixin, unittest.TestCase): @@ -343,5 +354,61 @@ def test_environment_preferred(self): self.assertEqual(webbrowser.get().name, sys.executable) -if __name__=='__main__': +class CliTest(unittest.TestCase): + def test_parse_args(self): + for command, url, new_window, new_tab in [ + # No optional arguments + ("https://example.com", "https://example.com", False, False), + # Each optional argument + ("https://example.com -n", "https://example.com", True, False), + ("-n https://example.com", "https://example.com", True, False), + ("https://example.com -t", "https://example.com", False, True), + ("-t https://example.com", "https://example.com", False, True), + # Long form + ("https://example.com --new-window", "https://example.com", True, False), + ("--new-window https://example.com", "https://example.com", True, False), + ("https://example.com --new-tab", "https://example.com", False, True), + ("--new-tab https://example.com", "https://example.com", False, True), + ]: + args = webbrowser.parse_args(shlex.split(command)) + + self.assertEqual(args.url, url) + self.assertEqual(args.new_window, new_window) + self.assertEqual(args.new_tab, new_tab) + + def test_parse_args_error(self): + for command in [ + # Arguments must not both be given + "https://example.com -n -t", + "https://example.com --new-window --new-tab", + "https://example.com -n --new-tab", + "https://example.com --new-window -t", + ]: + with self.assertRaises(SystemExit): + webbrowser.parse_args(shlex.split(command)) + + def test_main(self): + for command, expected_url, expected_new_win in [ + # No optional arguments + ("https://example.com", "https://example.com", 0), + # Each optional argument + ("https://example.com -n", "https://example.com", 1), + ("-n https://example.com", "https://example.com", 1), + ("https://example.com -t", "https://example.com", 2), + ("-t https://example.com", "https://example.com", 2), + # Long form + ("https://example.com --new-window", "https://example.com", 1), + ("--new-window https://example.com", "https://example.com", 1), + ("https://example.com --new-tab", "https://example.com", 2), + ("--new-tab https://example.com", "https://example.com", 2), + ]: + with ( + mock.patch("webbrowser.open", return_value=None) as mock_open, + mock.patch("builtins.print", return_value=None), + ): + webbrowser.main(shlex.split(command)) + mock_open.assert_called_once_with(expected_url, expected_new_win) + + +if __name__ == '__main__': unittest.main() From d502d2c83622510f73845b7409a51060ff0d907f Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 20 Mar 2024 00:14:11 +0200 Subject: [PATCH 11/15] Add blurb --- .../next/Library/2024-03-20-00-11-39.gh-issue-68583.mIlxxb.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-03-20-00-11-39.gh-issue-68583.mIlxxb.rst diff --git a/Misc/NEWS.d/next/Library/2024-03-20-00-11-39.gh-issue-68583.mIlxxb.rst b/Misc/NEWS.d/next/Library/2024-03-20-00-11-39.gh-issue-68583.mIlxxb.rst new file mode 100644 index 00000000000000..12caed75b79044 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-03-20-00-11-39.gh-issue-68583.mIlxxb.rst @@ -0,0 +1,2 @@ +webbrowser CLI: replace getopt with argparse, add long options. Patch by +Hugo van Kemenade. From 9cf39cdab823da46339140a4b829514bb8c2987c Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 10 Apr 2024 22:24:22 +0300 Subject: [PATCH 12/15] Simplify using action='store_const' and a common dest --- Lib/webbrowser.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 442c7d00279aef..254d485d6d5d47 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -682,9 +682,11 @@ def parse_args(arg_list: list[str] | None): parser.add_argument("url", help="URL to open") group = parser.add_mutually_exclusive_group() - group.add_argument("-n", "--new-window", action="store_true", + group.add_argument("-n", "--new-window", action="store_const", + const=1, dest="new_win", help="open new window") - group.add_argument("-t", "--new-tab", action="store_true", + group.add_argument("-t", "--new-tab", action="store_const", + const=2, dest="new_win", help="open new tab") args = parser.parse_args(arg_list) @@ -695,13 +697,7 @@ def parse_args(arg_list: list[str] | None): def main(arg_list: list[str] | None = None): args = parse_args(arg_list) - new_win = 0 - if args.new_window: - new_win = 1 - elif args.new_tab: - new_win = 2 - - open(args.url, new_win) + open(args.url, args.new_win) print("\a") From ab2ca6403c178f792d5f62b9d8fe5bef4df38d2b Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 10 Apr 2024 22:32:43 +0300 Subject: [PATCH 13/15] Break edited line to sub-80 cols --- Lib/webbrowser.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 254d485d6d5d47..b684142abba20e 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -522,7 +522,8 @@ def register_standard_browsers(): cmd = "xdg-settings get default-web-browser".split() raw_result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL) result = raw_result.decode().strip() - except (FileNotFoundError, subprocess.CalledProcessError, PermissionError, NotADirectoryError): + except (FileNotFoundError, subprocess.CalledProcessError, + PermissionError, NotADirectoryError): pass else: global _os_preferred_browser From 44a520062e5db0de2c4226afbf523cefe8f0f594 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 10 Apr 2024 22:44:25 +0300 Subject: [PATCH 14/15] Update tests and add default value --- Lib/test/test_webbrowser.py | 23 +++++++++++------------ Lib/webbrowser.py | 4 ++-- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py index 767539104f51d0..981d431815bc42 100644 --- a/Lib/test/test_webbrowser.py +++ b/Lib/test/test_webbrowser.py @@ -435,25 +435,24 @@ def test_environment_preferred(self): class CliTest(unittest.TestCase): def test_parse_args(self): - for command, url, new_window, new_tab in [ + for command, url, new_win in [ # No optional arguments - ("https://example.com", "https://example.com", False, False), + ("https://example.com", "https://example.com", 0), # Each optional argument - ("https://example.com -n", "https://example.com", True, False), - ("-n https://example.com", "https://example.com", True, False), - ("https://example.com -t", "https://example.com", False, True), - ("-t https://example.com", "https://example.com", False, True), + ("https://example.com -n", "https://example.com", 1), + ("-n https://example.com", "https://example.com", 1), + ("https://example.com -t", "https://example.com", 2), + ("-t https://example.com", "https://example.com", 2), # Long form - ("https://example.com --new-window", "https://example.com", True, False), - ("--new-window https://example.com", "https://example.com", True, False), - ("https://example.com --new-tab", "https://example.com", False, True), - ("--new-tab https://example.com", "https://example.com", False, True), + ("https://example.com --new-window", "https://example.com", 1), + ("--new-window https://example.com", "https://example.com", 1), + ("https://example.com --new-tab", "https://example.com", 2), + ("--new-tab https://example.com", "https://example.com", 2), ]: args = webbrowser.parse_args(shlex.split(command)) self.assertEqual(args.url, url) - self.assertEqual(args.new_window, new_window) - self.assertEqual(args.new_tab, new_tab) + self.assertEqual(args.new_win, new_win) def test_parse_args_error(self): for command in [ diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index b684142abba20e..b7fbc41853ea65 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -684,10 +684,10 @@ def parse_args(arg_list: list[str] | None): group = parser.add_mutually_exclusive_group() group.add_argument("-n", "--new-window", action="store_const", - const=1, dest="new_win", + const=1, default=0, dest="new_win", help="open new window") group.add_argument("-t", "--new-tab", action="store_const", - const=2, dest="new_win", + const=2, default=0, dest="new_win", help="open new tab") args = parser.parse_args(arg_list) From 9a4abb9511e12750ce3d3241b6b370b49b141340 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 11 Apr 2024 09:28:27 +0300 Subject: [PATCH 15/15] Ensure ambiguous shortening fails --- Lib/test/test_webbrowser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py index 981d431815bc42..849665294c3dfa 100644 --- a/Lib/test/test_webbrowser.py +++ b/Lib/test/test_webbrowser.py @@ -461,6 +461,8 @@ def test_parse_args_error(self): "https://example.com --new-window --new-tab", "https://example.com -n --new-tab", "https://example.com --new-window -t", + # Ensure ambiguous shortening fails + "https://example.com --new", ]: with self.assertRaises(SystemExit): webbrowser.parse_args(shlex.split(command))