-
-
Notifications
You must be signed in to change notification settings - Fork 31.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-68583: webbrowser: replace getopt
with argparse
, add long options
#117047
Changes from 12 commits
2428bba
ba99bcc
a1ebffc
c60dcae
9361e07
2915de7
fa90d15
fac943d
562a760
7ace992
d502d2c
a26c4b0
73b77e3
9cf39cd
ab2ca64
44a5200
9a4abb9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,13 +92,15 @@ 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. | ||
|
||
If not possible, then open url in the only browser window. | ||
""" | ||
return open(url, 1) | ||
|
||
|
||
def open_new_tab(url): | ||
"""Open url in a new page ("tab") of the default browser. | ||
|
||
|
@@ -136,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'] | ||
|
@@ -197,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 | ||
|
||
|
@@ -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 | ||
|
||
|
@@ -266,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] | ||
|
@@ -302,19 +310,20 @@ class Epiphany(UnixBrowser): | |
|
||
|
||
class Chrome(UnixBrowser): | ||
"Launcher class for Google Chrome browser." | ||
"""Launcher class for Google Chrome browser.""" | ||
terryjreedy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
remote_args = ['%action', '%s'] | ||
remote_action = "" | ||
remote_action_newwin = "--new-window" | ||
remote_action_newtab = "" | ||
background = True | ||
|
||
|
||
Chromium = Chrome | ||
|
||
|
||
class Opera(UnixBrowser): | ||
"Launcher class for Opera browser." | ||
"""Launcher class for Opera browser.""" | ||
|
||
remote_args = ['%action', '%s'] | ||
remote_action = "" | ||
|
@@ -324,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 = "" | ||
|
@@ -387,11 +396,11 @@ 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): | ||
"Launcher class for Microsoft Edge browser." | ||
"""Launcher class for Microsoft Edge browser.""" | ||
|
||
remote_args = ['%action', '%s'] | ||
remote_action = "" | ||
|
@@ -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")) | ||
|
||
|
@@ -514,7 +522,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): | ||
terryjreedy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
pass | ||
else: | ||
global _os_preferred_browser | ||
|
@@ -584,15 +592,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: | ||
|
@@ -667,33 +676,35 @@ def open(self, url, new=0, autoraise=True): | |
return 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) | ||
def parse_args(arg_list: list[str] | None): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's our current policy on stdlib type hints? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. python/devguide#1304 says in general don't add, but there are some specific exceptions, but that the policy is undocumented. I think these might be okay because they're "simple" and internal, but I'm also happy to remove if you'd prefer? |
||
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(arg_list) | ||
|
||
return args | ||
|
||
|
||
def main(arg_list: list[str] | None = None): | ||
args = parse_args(arg_list) | ||
|
||
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 | ||
terryjreedy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
open(args.url, new_win) | ||
|
||
print("\a") | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
webbrowser CLI: replace getopt with argparse, add long options. Patch by | ||
Hugo van Kemenade. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
argparse
allows argument shortening and handles ambiguous shortenings as one might hope, but it might be nice to confirm that--new
fails properly.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, I didn't know about that feature :) I've added a test case.