├── .flake8
├── .gitignore
├── .python-version
├── CODEOWNERS
├── Context.sublime-menu
├── Default (OSX).sublime-keymap
├── Default.sublime-keymap
├── Default.sublime-mousemap
├── LICENSE
├── README.md
├── __init__.py
├── messages.json
├── messages
├── 2.1.1.md
├── 2.2.0.md
├── 2.3.0.md
├── 2.4.0.md
├── 2.5.0.md
├── 2.6.0.md
├── 2.7.0.md
├── 2.8.0.md
└── install.md
├── open_url.py
├── open_url.sublime-commands
├── open_url.sublime-settings
├── pre-push
├── pyproject.toml
├── pyrightconfig.json
└── url.py
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | ignore =
3 | C812,
4 | C813,
5 | C814,
6 | E226
7 | max-line-length = 120
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.sublime-project
2 | *.sublime-workspace
3 | *.pyc
4 |
--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------
1 | 3.8
2 |
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @noahcoad @kylebebak
2 |
--------------------------------------------------------------------------------
/Context.sublime-menu:
--------------------------------------------------------------------------------
1 | [{ "command": "open_url" }]
2 |
--------------------------------------------------------------------------------
/Default (OSX).sublime-keymap:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "keys": [
4 | "ctrl+u"
5 | ],
6 | "command": "open_url",
7 | "context": [
8 | {
9 | "key": "setting.open_url.disable_default_key_bindings",
10 | "operator": "not_equal",
11 | "operand": true
12 | }
13 | ]
14 | }
15 | ]
16 |
--------------------------------------------------------------------------------
/Default.sublime-keymap:
--------------------------------------------------------------------------------
1 | // Linux and Windows
2 | [
3 | {
4 | "keys": [
5 | "ctrl+alt+u"
6 | ],
7 | "command": "open_url",
8 | "context": [
9 | {
10 | "key": "setting.open_url.disable_default_key_bindings",
11 | "operator": "not_equal",
12 | "operand": true
13 | }
14 | ]
15 | }
16 | ]
17 |
--------------------------------------------------------------------------------
/Default.sublime-mousemap:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "button": "button1",
4 | "count": 2,
5 | "modifiers": ["alt"],
6 | "press_command": "drag_select",
7 | "command": "open_url"
8 | }
9 | ]
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 Noah Coad
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Open URL
2 |
3 | ## Description
4 |
5 | Quickly open files, folders, web URLs or other URLs from anywhere in Sublime Text.
6 |
7 | ## Install
8 |
9 | Look for **Open URL** using the [Package Manager](http://wbond.net/sublime_packages/package_control)
10 |
11 | ## How to use
12 |
13 | Put the cursor inside a **file** / **folder** / **URL** / **word** and run the command. It automatically expands the selection until it hits **delimiter** chars, which can be changed in the settings (see below).
14 |
15 | Alternatively, highlight the text you want to open. If text is highlighted the selection is not expanded.
16 |
17 | Here's a bunch of ways you can run the command.
18 |
19 | - ctrl+u (OSX), ctrl+alt+u (Linux/Windows)
20 | - right-click > **Open URL**
21 | - alt + double-click
22 | - shift+cmd+p, then look for **Open URL**
23 |
24 | ### Give it a try
25 |
26 | Copy the items below to Sublime Text. Place your cursor inside any one of them and hit ctrl+u for OSX, or ctrl+alt+u for Linux/Windows.
27 |
28 | - $HOME/Desktop
29 | -
30 | - google.com
31 | - search_for_me
32 |
33 | ### How does it work?
34 |
35 | If your selection is a **file** or a **folder**, you can choose to **edit** it (open with Sublime Text), or **reveal** it (open with macOS Finder/Windows File Explorer/Linux File Manager).
36 |
37 | Opening files and folders is super convenient. Both can be specified with absolute paths, paths relative to the currently open file, or paths relative to the root of the currently open project. Env vars and the alias `~` are expanded.
38 |
39 | If your selection is a URL, it opens immediately in a new tab in your default web browser. You can omit the scheme (http://) if you want and **Open URL** will add it for you.
40 |
41 | > If Open URL fails to open a web URL, you might have to change the path to your web browser executable. See the **web_browser_path** setting below.
42 |
43 | If your selection is none of the above, and you haven't configured custom commands for special URLs using `other_custom_commands`, you'll be presented with two options:
44 |
45 | - modify the selection and try again
46 | - search for the selection using one of your configured **web_searchers**
47 | - the only web searcher that ships with **Open URL** is Google search
48 | - to add others, read more in the "Settings" section below
49 |
50 | ### Shortcuts
51 |
52 | Don't want to choose from menu items to open a file or a folder? Look for **Open URL (Skip Menu)** in the Command Palette. To create a key binding for this, open **Preferences: Key Bindings** from the Command Palette, and add the following:
53 |
54 | ```json
55 | { "keys": ["your+key+binding"], "command": "open_url", "args": { "show_menu": false } },
56 | ```
57 |
58 | This will open files in Sublime Text for editing, or reveal folders in the Finder, without showing the menu first.
59 |
60 | ### Running shell commands on files, folders or special URLs
61 |
62 | **Open URL** provides a few settings you can configure to run custom shell commands on files, folders, or special URLs, such as FTP URLs:
63 |
64 | - **file_custom_commands**
65 | - **folder_custom_commands**
66 | - **other_custom_commands** (for special URLs, i.e. neither files, folders, nor web URLs)
67 |
68 | The custom command settings should point to an array of objects that can have up to 5 properties:
69 |
70 | - `label`, **required**: the label for the command in the dropdown menu
71 | - `commands` **required**: a string, or an array of shell arguments, to which the URL is appended; if string/array contains the `$url` placeholder, this placeholder is replaced with the URL, and URL is not appended to end of string/array
72 | - `pattern`, **optional**: the command only appears if the URL matches this pattern
73 | - `os` **optional**: the command only appears for this OS; one of `('osx', 'windows', 'linux')`
74 | - `kwargs`, **optional**: kwargs that are passed to [subprocess.Popen](https://docs.python.org/3.5/library/subprocess.html#popen-constructor)
75 |
76 | For example, the **reveal** command for files uses the following `file_custom_commands`.
77 |
78 | ```json
79 | "file_custom_commands": [
80 | { "label": "reveal", "os": "osx", "commands": ["open", "-R"] },
81 | { "label": "reveal", "os": "windows", "commands": ["explorer", "/select,"] },
82 | { "label": "reveal", "os": "linux", "commands": ["nautilus", "--browser"] },
83 | ],
84 | ```
85 |
86 | For another example, if you wanted to create OSX commands for adding a folder to the current project or for opening a folder in a new window, you could do something like this:
87 |
88 | ```json
89 | "folder_custom_commands": [
90 | { "label": "add to project", "os": "osx", "commands": ["open", "-a", "Sublime Text"] },
91 | { "label": "open in new window", "os": "osx", "commands": ["/usr/local/bin/subl"] },
92 | ],
93 | ```
94 |
95 | #### Set cwd directory for shell command
96 |
97 | You might want to choose the directory from which your shell command is executed. Python's `subprocess` library makes this easy with the `cwd` kwarg.
98 |
99 | Open URL defines two special values for the `cwd` kwarg, `"project_root"` and `"current_file"`. Using these values dynamically sets the working directory for the shell command to the project root, or the directory of the currently open file.
100 |
101 | Check the **Settings** section, or run **Open URL: Settings** for examples.
102 |
103 | ### URL / Path Transforms
104 |
105 | Open URL has settings that let you transform your selected URL / path before attempting to open it.
106 |
107 | Here are the settings with their default values:
108 |
109 | - `aliases`: `{}`
110 | - `search_paths`: `["src"]`
111 | - `file_prefixes`: `[]`
112 | - `file_suffixes`: `[".js"]`
113 |
114 | The `aliases` dict is the first transform applied to the selected URL / path. It replaces each **key** in URL with the corresponding **value**.
115 |
116 | The other transforms affect only file and folder paths. `search_paths` is a list of directories that are prepended to the path, `file_prefixes` are prepended to the filename, and `file_suffixes` are appended to the filename.
117 |
118 | One path is generated for each combination of search path, file prefix and file suffix, and the first path that contains a directory or a file is opened.
119 |
120 | Imagine you're building a JS app that you've set up to use absolute imports, relative to the `src` directory. Your app has a file at `src/utils/module.js`. Open URL can resolve this file using just `utils/module`. Very nice!
121 |
122 | ### Multiple Cursors
123 |
124 | Copy these URLs into Sublime Text and select both lines using multiple cursors, then run URL opener.
125 |
126 | -
127 | -
128 |
129 | The plugin opens both URLs simultaneously. You can use multiple cursors to open multiple files, folders, URLs, or a mix of all of them. Note that running Open URL with multiple cursors will skip the menu, as if you had run **Open URL (Skip Menu)**, for all selections.
130 |
131 | ## Settings
132 |
133 | To customize these, hit shift+cmd+p to open the Command Palette, and look for **Open URL: Settings**.
134 |
135 | - **delimiters**
136 | - characters at which auto-expansion of selected path stops, e.g. `` \t\n\r\"'`,*<>[](){}``
137 | - the default settings are Markdown friendly
138 | - **trailing_delimiters**
139 | - if any of these characters are seen at the end of a URL, they are recursively removed; for file and folder paths, URLs **with and without** trailing delimiters are tried; default is `;.:`
140 | - **web_browser**
141 | - the browser that Open URL uses to open new tabs; must be a string [from this list](https://docs.python.org/3.3/library/webbrowser.html)
142 | - if you use an empty string, the "default browser" will be used
143 | - if you choose a browser that's not installed on your machine, Open URL will complain
144 | - **web_browser_path**
145 | - the path to your web browser executable for opening web URLs
146 | - this setting overrides the default web browser and the **web_browser** setting
147 | - [read the top answer here](https://stackoverflow.com/questions/22445217/python-webbrowser-open-to-open-chrome-browser), or look in settings for examples
148 | - **web_searchers**
149 | - if your selection isn't a file, a folder, or a URL, you can choose to pass it to a web searcher, which is just a URL that searches for the selected text
150 | - example: `{ "label": "google search", "url": "http://google.com/search?q=", "encoding": "utf-8" }`
151 | - **aliases**
152 | - first transform applied to URL, a dict with keys and values; replace each **key** in URL with corresponding **value**
153 | - example: `{ "{{BASE_PATH}}": "src/base" }`
154 | - **search_paths**
155 | - path transform; joins these directories to beginning of path
156 | - example: `["src"]`
157 | - **file_prefixes**
158 | - path transform; adds these prefixes to filename only
159 | - example: `["_"]`
160 | - **file_suffixes**
161 | - path transform; adds these suffixes to filename only
162 | - example: `[".js", ".ts", ".tsx"]`
163 | - **file_custom_commands**
164 | - pass a file to shell commands whose pattern matches the file path
165 | - example, for copying the file path to the clipboard: `{ "label": "copy path", "commands": "printf '$url' | pbcopy" }`
166 | - **folder_custom_commands**
167 | - pass a folder to shell commands whose pattern matches the folder path
168 | - example, for opening the folder in iTerm: `{ "label": "open in terminal", "commands": [ "open", "-a", "/Applications/iTerm.app" ] }`
169 | - **other_custom_commands**
170 | - pass a URL which is neither a file, a folder, nor a web URL to shell commands whose pattern matches the URL
171 | - example, for opening a file at a specific line number: `{ "label": "subl: open file at line #", "pattern": ":[0-9]+$", "commands": [ "/usr/local/bin/subl" ], "kwargs": {"cwd": "project_root"} }`
172 |
173 | ### Project-Specific Settings
174 |
175 | Some settings, especially the URL / path transforms like `aliases`, will probably vary between projects. Fortunately Open URL lets you specify project-specific settings in any `.sublime-project` file. Just put them in `["settings"]["open_url"]`.
176 |
177 | ```json
178 | {
179 | "folders": [
180 | {
181 | "path": "~/Library/Application Support/Sublime Text 3/Packages/OpenUrl"
182 | }
183 | ],
184 | "settings": {
185 | "open_url": {
186 | "file_suffixes": [".py"]
187 | }
188 | }
189 | }
190 | ```
191 |
192 | Project-specific settings override default and user settings.
193 |
194 | ### Disable default key bindings
195 |
196 | To do this, add `"open_url.disable_default_key_bindings": true` to `Preferences.sublime-settings`.
197 |
198 | ## Release Notes
199 |
200 | [See Open URL's version history here](https://github.com/noahcoad/open-url/tree/master/messages).
201 |
202 | ## Development
203 |
204 | If you use `pyenv`, [the `3.8` version](https://www.sublimetext.com/docs/api_environments.html) in the `.python-version` file will cause problems because it's not a valid `pyenv` version.
205 |
206 | To fix this, install some version `3.8.X` with `pyenv`, then run `ln -s ~/.pyenv/versions/3.8.X ~/.pyenv/versions/3.8`.
207 |
208 | ## Finally
209 |
210 | See also: [Google Spell Check](https://github.com/noahcoad/google-spell-check).
211 |
212 | Credits: Thanks goes to peterc for starting [a forum thread](http://www.sublimetext.com/forum/viewtopic.php?f=2&t=4243) about this topic and KatsuomiK for [his gist](https://gist.github.com/3542836), which were the inspiration for this plugin.
213 |
214 | Author: [@noahcoad](http://twitter.com/noahcoad) writes software for the heck of it and to make life just a little more efficient.
215 |
216 | Maintainer: [@kylebebak](https://github.com/kylebebak).
217 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noahcoad/open-url/56115b21f42735ea5c2e1576704e1d904d7ac6dc/__init__.py
--------------------------------------------------------------------------------
/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "install": "messages/install.md",
3 | "2.1.1": "messages/2.1.1.md",
4 | "2.2.0": "messages/2.2.0.md",
5 | "2.3.0": "messages/2.3.0.md",
6 | "2.4.0": "messages/2.4.0.md",
7 | "2.5.0": "messages/2.5.0.md",
8 | "2.6.0": "messages/2.6.0.md",
9 | "2.7.0": "messages/2.7.0.md",
10 | "2.8.0": "messages/2.8.0.md"
11 | }
12 |
--------------------------------------------------------------------------------
/messages/2.1.1.md:
--------------------------------------------------------------------------------
1 | # 2.1.1
2 |
3 | ## Big News
4 |
5 | Open URL has another maintainer! @noahcoad is letting me continue development on the awesome Open URL plugin. In the last few months I've added a lot of new features.
6 |
7 | I'll also work to handle outstanding issues and merge pull requests, if they haven't been addressed by features that were added.
8 |
9 | Support for Sublime Text 2 has officially been dropped, which I hope will affect precisely none of you.
10 |
11 | Instead of trying to summarize the new stuff just have a look at the README: https://github.com/noahcoad/open-url
12 |
13 | Thanks and happy coding!
14 | @kylebebak
15 |
--------------------------------------------------------------------------------
/messages/2.2.0.md:
--------------------------------------------------------------------------------
1 | # 2.2.0
2 |
3 | ## Bug Fix
4 |
5 | The "reveal" option doesn't appear 3 consecutive times for opening folders, because `folder_custom_commands` are now passed to `match_openers`.
6 |
7 | ## New Feature
8 |
9 | The `commands` key in custom commands can now also be a string instead of just an array. If it's a string, the URL can be injected into it at any point by using the `$url` placeholder in the string; this placeholder is simply replaced with the URL.
10 |
11 | This makes it possible, for example, to use `pbcopy` to create a custom command for copying the full path of a file to the clipboard. Check the README for more information.
12 |
--------------------------------------------------------------------------------
/messages/2.3.0.md:
--------------------------------------------------------------------------------
1 | # 2.3.0
2 |
3 | ## New Feature
4 |
5 | The `cwd` kwarg for custom commands has special handling for two values: "project_root" and "current_file".
6 |
7 | This lets you dynamically set the directory from which custom shell commands are executed, to either the project root or the directory of the currently open file.
8 |
9 | Any other value for `cwd`, e.g. "/Users/myname/code/project", is unmodified. This is currently being used by the **subl: open file at line #** command.
10 |
11 | ```
12 | { "label": "subl: open file at line #", "pattern": ":[0-9]+$", "commands": [ "subl" ], "kwargs": {"cwd": "project_root"} },
13 | ```
14 |
15 | This custom command uses the [subl executable](http://docs.sublimetext.info/en/latest/command_line/command_line.html) to open a file at a specific line number, such as:
16 |
17 | myfile.txt:21
18 |
19 | Give it a try!
20 |
--------------------------------------------------------------------------------
/messages/2.4.0.md:
--------------------------------------------------------------------------------
1 | # 2.4.0
2 |
3 | ## New Feature
4 |
5 | Open URL now works for multiple cursors!
6 |
7 | This works for files, folders and URLs. Try selecting both of these URLs using multiple cursors, and running Open URL.
8 |
9 | - https://www.google.com?q=hello
10 | - https://www.google.com?q=there
11 |
--------------------------------------------------------------------------------
/messages/2.5.0.md:
--------------------------------------------------------------------------------
1 | # 2.5.0
2 |
3 | ## New Features
4 |
5 | Running Open URL on an empty URL in a saved file passes the file's path to your file commands.
6 |
7 | If you're in Sublime Text project, running Open URL on an empty URL in an **unsaved view** passes the project's root directory to your folder commands.
8 |
--------------------------------------------------------------------------------
/messages/2.6.0.md:
--------------------------------------------------------------------------------
1 | # 2.6.0
2 |
3 | ## Path transform settings
4 |
5 | Open URL just got a whole lot more powerful!
6 |
7 | This release adds the following path transform settings: `aliases`, `search_paths`, `file_prefixes`, `file_suffixes`, based on these issues:
8 |
9 | - https://github.com/noahcoad/open-url/issues/49
10 | - https://github.com/noahcoad/open-url/issues/50
11 |
12 | These settings let you apply a combination of transformations to the file/folder path and try to open each of them. This lets you do things like this:
13 |
14 | ```
15 | "search_paths": ["src"],
16 | "file_suffixes": [".js"],
17 | ```
18 |
19 | Open URL can now open a file at `src/utils/module.js` with this path: `utils/module`. Very nice for absolute imports and languages with modules!
20 |
21 | Check out for examples on how to use the new settings.
22 |
--------------------------------------------------------------------------------
/messages/2.7.0.md:
--------------------------------------------------------------------------------
1 | # 2.7.0
2 |
3 | ## Remove trailing delimiters
4 |
5 | Just got more useful. Trailing delimiters removed from file and folder paths as well, and the resulting paths **with and without** removed delimiters are tried.
6 |
--------------------------------------------------------------------------------
/messages/2.8.0.md:
--------------------------------------------------------------------------------
1 | # 2.8.0
2 |
3 | ## Allow search on matched file or directory
4 |
5 | You can search for a path using searchers if even the path matches a file or directory on your file system.
6 |
7 | Sublime Text 3 tags are now prefixed with `st3-`. The Sublime Text 4 versions of the plugin have normal semver release tags.
8 |
--------------------------------------------------------------------------------
/messages/install.md:
--------------------------------------------------------------------------------
1 | # Welcome to Open URL
2 |
3 | ## Description
4 |
5 | Quickly open files, folders, or web URLs from anywhere in Sublime Text.
6 |
7 | ## Install
8 |
9 | Look for **Open URL** using the [Package Manager](http://wbond.net/sublime_packages/package_control)
10 |
11 | ## How to use
12 |
13 | See here: https://github.com/noahcoad/open-url
14 |
15 | ## Release Notes
16 |
17 | See here: https://github.com/noahcoad/open-url/tree/master/messages
18 |
--------------------------------------------------------------------------------
/open_url.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import os
4 | import re
5 | import subprocess
6 | import threading
7 | import webbrowser
8 | from typing import TypedDict, cast
9 | from urllib.parse import quote, urlparse
10 |
11 | import sublime # type: ignore
12 | import sublime_plugin # type: ignore
13 |
14 | from .url import is_url
15 |
16 | Settings = TypedDict(
17 | "Settings",
18 | {
19 | "delimiters": str,
20 | "trailing_delimiters": str,
21 | "web_browser": str,
22 | "web_browser_path": list,
23 | "web_searchers": list,
24 | "file_prefixes": list,
25 | "file_suffixes": list,
26 | "search_paths": list,
27 | "aliases": dict,
28 | "file_custom_commands": list,
29 | "folder_custom_commands": list,
30 | "other_custom_commands": list,
31 | },
32 | )
33 |
34 | # these are necessary to convert settings object to a dict, which can then be merged with project settings
35 | settings_keys = [
36 | "delimiters",
37 | "trailing_delimiters",
38 | "web_browser",
39 | "web_browser_path",
40 | "web_searchers",
41 | "file_prefixes",
42 | "file_suffixes",
43 | "search_paths",
44 | "aliases",
45 | "file_custom_commands",
46 | "folder_custom_commands",
47 | "other_custom_commands",
48 | ]
49 |
50 |
51 | def prepend_scheme(s: str) -> str:
52 | o = urlparse(s)
53 | if not o.scheme:
54 | s = "http://" + s
55 | return s
56 |
57 |
58 | def remove_trailing_delimiters(url: str, trailing_delimiters: str) -> str:
59 | """
60 | Removes any and all chars in trailing_delimiters from end of url.
61 | """
62 | if not trailing_delimiters:
63 | return url
64 | while url:
65 | if url[-1] in trailing_delimiters:
66 | url = url[:-1]
67 | else:
68 | break
69 | return url
70 |
71 |
72 | def match_openers(openers: list[dict], url: str) -> list[dict]:
73 | ret: list[dict] = []
74 | platform = sublime.platform()
75 | for opener in openers:
76 | pattern: str | None = opener.get("pattern")
77 | o_s: str | None = opener.get("os")
78 | if pattern and not re.search(pattern, url):
79 | continue
80 | if o_s and not o_s.lower() == platform:
81 | continue
82 | ret.append(opener)
83 | return ret
84 |
85 |
86 | def resolve_aliases(url: str, aliases: dict) -> str:
87 | for key, val in aliases.items():
88 | url = url.replace(key, val)
89 | return url
90 |
91 |
92 | def generate_urls(
93 | url: str, search_paths: list[str], file_prefixes: list[str], file_suffixes: list[str], trailing_delimiters: str
94 | ):
95 | urls: list[str] = []
96 |
97 | bare_urls = [url]
98 | clean = remove_trailing_delimiters(url, trailing_delimiters)
99 | if clean != url:
100 | bare_urls.append(clean)
101 |
102 | for u in bare_urls:
103 | for path in [""] + search_paths:
104 | d, base = os.path.split(os.path.join(path, u))
105 | for prefix in [""] + file_prefixes:
106 | for suffix in [""] + file_suffixes:
107 | urls.append(os.path.join(d, prefix + base + suffix))
108 | return urls
109 |
110 |
111 | def merge_settings(window, keys: list[str]) -> Settings:
112 | settings_object = sublime.load_settings("open_url.sublime-settings")
113 | settings = cast(Settings, {k: settings_object.get(k) for k in keys})
114 |
115 | project = window.project_data()
116 | if project is None:
117 | return settings
118 | try:
119 | for k, v in project["settings"]["open_url"].items():
120 | settings[k] = v
121 | return settings
122 | except Exception:
123 | return settings
124 |
125 |
126 | class OpenUrlCommand(sublime_plugin.TextCommand):
127 | config: Settings
128 |
129 | def run(
130 | self,
131 | edit=None,
132 | url: str | None = None,
133 | show_menu: bool = True,
134 | show_input: bool = False,
135 | ) -> None:
136 | self.config = merge_settings(self.view.window(), settings_keys)
137 |
138 | if show_input:
139 |
140 | def on_done(input_url: str):
141 | self.handle(input_url, show_menu)
142 |
143 | self.view.window().show_input_panel("Path:", "", on_done, None, None)
144 | return
145 |
146 | # Sublime Text has its own open_url command used for things like Help > Documentation
147 | # so if a url is passed, open it instead of getting text from the view
148 | if url is not None:
149 | urls = [url]
150 | else:
151 | urls = [self.get_selection(region) for region in self.view.sel()]
152 | if len(urls) > 1:
153 | show_menu = False
154 | for url in urls:
155 | self.handle(url, show_menu)
156 |
157 | def handle(self, url: str, show_menu: bool) -> None:
158 | url = resolve_aliases(url, self.config["aliases"])
159 | urls = generate_urls(
160 | url,
161 | self.config["search_paths"],
162 | self.config["file_prefixes"],
163 | self.config["file_suffixes"],
164 | self.config["trailing_delimiters"],
165 | )
166 |
167 | for u in urls:
168 | path = self.abs_path(u)
169 |
170 | if os.path.isfile(path):
171 | self.file_action(path, show_menu, u)
172 | return
173 |
174 | if self.view.file_name() and not u:
175 | # open current file if url is empty
176 | self.file_action(self.view.file_name(), show_menu, self.view.file_name())
177 | return
178 |
179 | if os.path.isdir(path):
180 | self.folder_action(path, show_menu, u)
181 | return
182 |
183 | clean_path = remove_trailing_delimiters(url, self.config["trailing_delimiters"])
184 | if is_url(clean_path) or clean_path.startswith("http://") or clean_path.startswith("https://"):
185 | self.open_tab(prepend_scheme(clean_path))
186 | return
187 |
188 | openers = match_openers(self.config["other_custom_commands"], clean_path)
189 | if openers:
190 | self.other_action(clean_path, openers, show_menu)
191 | return
192 |
193 | self.modify_or_search_action(url)
194 |
195 | def get_selection(self, region) -> str:
196 | """Returns selection. If selection contains no characters, expands it
197 | until hitting delimiter chars.
198 | """
199 | start: int = region.begin()
200 | end: int = region.end()
201 |
202 | if start != end:
203 | sel: str = self.view.substr(sublime.Region(start, end))
204 | return sel.strip()
205 |
206 | # nothing is selected, so expand selection to nearest delimiters
207 | view_size: int = self.view.size()
208 | delimiters = list(self.config["delimiters"])
209 |
210 | # move the selection back to the start of the url
211 | while start > 0:
212 | if self.view.substr(start - 1) in delimiters:
213 | break
214 | start -= 1
215 |
216 | # move end of selection forward to the end of the url
217 | while end < view_size:
218 | if self.view.substr(end) in delimiters:
219 | break
220 | end += 1
221 | sel = self.view.substr(sublime.Region(start, end))
222 | return sel.strip()
223 |
224 | def file_path(self):
225 | path = self.view.file_name()
226 | if path: # this file has been saved to disk
227 | return os.path.dirname(path)
228 | return None
229 |
230 | def project_path(self):
231 | project = self.view.window().project_data()
232 | if project is None:
233 | return None
234 | try:
235 | return os.path.expanduser(project["folders"][0]["path"])
236 | except Exception:
237 | return None
238 |
239 | def abs_path(self, path: str) -> str:
240 | """Normalizes path, and attempts to convert path into absolute path."""
241 | path = os.path.normcase(os.path.expandvars(os.path.expanduser(path)))
242 | if os.path.isabs(path):
243 | return path
244 |
245 | file_path = self.file_path()
246 | if file_path:
247 | abs_path = os.path.join(file_path, path)
248 | if os.path.exists(abs_path): # if file relative to current view exists, open it, else continue
249 | return abs_path
250 |
251 | project_path = self.project_path()
252 | if project_path is None: # nothing more to try
253 | return path
254 | return os.path.join(project_path, path)
255 |
256 | def prepare_args_and_run(self, opener: dict, path: str):
257 | commands = opener.get("commands", [])
258 | kwargs = opener.get("kwargs", {})
259 |
260 | cwd = kwargs.get("cwd")
261 | if cwd == "project_root":
262 | project_path = self.project_path()
263 | if project_path:
264 | kwargs["cwd"] = project_path
265 | if cwd == "current_file":
266 | file_path = self.file_path()
267 | if file_path:
268 | kwargs["cwd"] = file_path
269 |
270 | if isinstance(commands, str):
271 | kwargs["shell"] = True
272 | if "$url" in commands:
273 | self.run_subprocess(commands.replace("$url", path), kwargs)
274 | else:
275 | self.run_subprocess(f"{commands} {path}", kwargs)
276 | else:
277 | has_url = any("$url" in command for command in commands)
278 | if has_url:
279 | self.run_subprocess([command.replace("$url", path) for command in commands], kwargs)
280 | else:
281 | self.run_subprocess(commands + [path], kwargs)
282 |
283 | def run_subprocess(self, args, kwargs):
284 | """Runs on another thread to avoid blocking main thread."""
285 |
286 | def sp(args, kwargs):
287 | subprocess.check_call(args, **kwargs)
288 |
289 | threading.Thread(target=sp, args=(args, kwargs)).start()
290 |
291 | def open_tab(self, url: str) -> None:
292 | browser = self.config["web_browser"]
293 | browser_path = self.config["web_browser_path"]
294 |
295 | def ot(url, browser, browser_path):
296 | if browser_path:
297 | if not webbrowser.get(browser_path).open(url):
298 | sublime.error_message(f'Could not open tab using your "web_browser_path" setting: {browser_path}')
299 | return
300 | try:
301 | controller = webbrowser.get(browser or None)
302 | except Exception:
303 | e = 'Python couldn\'t find the "{}" browser. Change "web_browser" in Open URL\'s settings.'
304 | sublime.error_message(e.format(browser or "default"))
305 | return
306 | controller.open_new_tab(url)
307 |
308 | threading.Thread(target=ot, args=(url, browser, browser_path)).start()
309 |
310 | def modify_or_search_action(self, term: str):
311 | """Not a URL and not a local path; prompts user to modify path and looks
312 | for it again, or searches for this term using a web searcher.
313 | """
314 | searchers = self.config["web_searchers"]
315 | opts = [f"modify path {term}"]
316 | opts += [f'{s["label"]} ({term})' for s in searchers]
317 | sublime.active_window().show_quick_panel(opts, lambda idx: self.modify_or_search_done(idx, searchers, term))
318 |
319 | def modify_or_search_done(self, idx: int, searchers, term: str):
320 | if idx < 0:
321 | return
322 | if idx == 0:
323 | self.view.window().show_input_panel("URL or path:", term, self.url_search_modified, None, None)
324 | return
325 | idx -= 1
326 | searcher = searchers[idx]
327 | self.open_tab(
328 | "{}{}".format(
329 | searcher.get("url"),
330 | quote(term.encode(searcher.get("encoding", "utf-8"))),
331 | )
332 | )
333 |
334 | def url_search_modified(self, text: str):
335 | """Call open_url again on modified path."""
336 | try:
337 | self.view.run_command("open_url", {"url": text})
338 | except ValueError:
339 | pass
340 |
341 | def other_action(self, path: str, openers: list[dict], show_menu: bool):
342 | if openers and not show_menu:
343 | self.other_done(0, openers, path)
344 | return
345 |
346 | opts = [opener.get("label") for opener in openers]
347 | sublime.active_window().show_quick_panel(opts, lambda idx: self.other_done(idx, openers, path))
348 |
349 | def other_done(self, idx, openers, path):
350 | if idx < 0:
351 | return
352 | opener = openers[idx]
353 | self.prepare_args_and_run(opener, path)
354 |
355 | def folder_action(self, folder: str, show_menu: bool, raw_folder: str):
356 | """Choose from folder actions."""
357 | openers = match_openers(self.config["folder_custom_commands"], folder)
358 |
359 | if openers and not show_menu:
360 | self.folder_done(0, openers, folder, raw_folder)
361 | return
362 |
363 | opts = [*[opener.get("label") for opener in openers], "search..."]
364 | sublime.active_window().show_quick_panel(opts, lambda idx: self.folder_done(idx, openers, folder, raw_folder))
365 |
366 | def folder_done(self, idx: int, openers: list[dict], folder: str, raw_folder: str):
367 | if idx < 0:
368 | return
369 | if idx >= len(openers):
370 | self.modify_or_search_action(raw_folder)
371 |
372 | opener = openers[idx]
373 | if sublime.platform() == "windows":
374 | folder = os.path.normcase(folder)
375 | self.prepare_args_and_run(opener, folder)
376 |
377 | def file_action(self, path: str, show_menu: bool, raw_path: str) -> None:
378 | """Edit file or choose from file actions."""
379 | openers = match_openers(self.config["file_custom_commands"], path)
380 |
381 | if not show_menu:
382 | self.view.window().open_file(path)
383 | return
384 |
385 | sublime.active_window().show_quick_panel(
386 | ["edit", *[opener.get("label") for opener in openers], "search..."],
387 | lambda idx: self.file_done(idx, openers, path, raw_path),
388 | )
389 |
390 | def file_done(self, idx: int, openers: list[dict], path: str, raw_path: str):
391 | if idx < 0:
392 | return
393 | if idx == 0:
394 | self.view.window().open_file(path)
395 | return
396 | if idx >= len(openers) + 1:
397 | self.modify_or_search_action(raw_path)
398 |
399 | opener = openers[idx - 1]
400 | if sublime.platform() == "windows":
401 | path = os.path.normcase(path)
402 | self.prepare_args_and_run(opener, path)
403 |
--------------------------------------------------------------------------------
/open_url.sublime-commands:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "caption": "Open URL: Settings",
4 | "command": "edit_settings",
5 | "args": {
6 | "base_file": "${packages}/Open URL/open_url.sublime-settings",
7 | "default": "// settings in here override those in \"Open URL/open_url.sublime-settings\",\n\n{\n\t$0\n}\n"
8 | }
9 | },
10 |
11 | { "caption": "Open URL", "command": "open_url" },
12 |
13 | {
14 | "caption": "Open URL (Skip Menu)",
15 | "command": "open_url",
16 | "args": { "show_menu": false }
17 | },
18 | {
19 | "caption": "Open URL (Use Input)",
20 | "command": "open_url",
21 | "args": { "show_input": true }
22 | }
23 | ]
24 |
--------------------------------------------------------------------------------
/open_url.sublime-settings:
--------------------------------------------------------------------------------
1 | // to specify your own, either edit this file, or copy/paste to open_url.sublime-settings file in your User folder
2 | // if there is an open_url.sublime-settings in the User folder, Sublime Text merges those settings with these
3 | {
4 | // delimiters for expanding selection, these are markdown friendly
5 | "delimiters": " \t\n\r\"'`,*<>[](){}",
6 |
7 | // if these delimiting characters are seen at the end of the URL, they are removed, e.g. sublimetext.com.; becomes sublimetext.com
8 | "trailing_delimiters": ";.:",
9 |
10 | // the browser that Open URL uses to open new tabs
11 | // it can be an empty string, or a string [from this list](https://docs.python.org/3.3/library/webbrowser.html)
12 | "web_browser": "",
13 |
14 | // the path to the executable that Open URL uses to open new tabs; overrides "web_browser"
15 | "web_browser_path": "",
16 | // example: chrome, osx
17 | // "web_browser_path": "open -a /Applications/Google\\ Chrome.app %s"
18 | // example: chrome, windows
19 | // "web_browser_path": "C:/Program Files (x86)/Google/Chrome/Application/chrome.exe %s"
20 | // example: chrome, linux
21 | // "web_browser_path": "/usr/bin/google-chrome %s"
22 |
23 | // if URL is neither a local file nor a web URL, pass it to a web searcher
24 | "web_searchers": [
25 | {
26 | "label": "google search",
27 | "url": "http://google.com/search?q=",
28 | "encoding": "utf-8"
29 | }
30 | ],
31 |
32 | // path transforms
33 | "aliases": {},
34 |
35 | "search_paths": ["src"],
36 |
37 | "file_prefixes": [],
38 |
39 | "file_suffixes": [".js"],
40 |
41 | // pass file that matches regex "pattern" to shell commands
42 | "file_custom_commands": [
43 | { "label": "reveal", "os": "osx", "commands": ["open", "-R"] },
44 | {
45 | "label": "open with default application",
46 | "os": "osx",
47 | "commands": ["open"]
48 | },
49 |
50 | {
51 | "label": "reveal",
52 | "os": "windows",
53 | "commands": ["explorer", "/select,"]
54 | },
55 | {
56 | "label": "open with default application",
57 | "os": "windows",
58 | "commands": ["start"],
59 | "kwargs": { "shell": true }
60 | },
61 |
62 | { "label": "reveal", "os": "linux", "commands": ["nautilus", "--browser"] },
63 | { "label": "open with default application", "os": "linux", "commands": [] }
64 | ],
65 |
66 | // pass folder that matches regex "pattern" to shell commands
67 | "folder_custom_commands": [
68 | { "label": "reveal", "os": "osx", "commands": ["open"] },
69 |
70 | { "label": "reveal", "os": "windows", "commands": ["explorer"] },
71 |
72 | { "label": "reveal", "os": "linux", "commands": ["nautilus", "--browser"] },
73 | {
74 | "label": "open in new window",
75 | "os": "osx",
76 | "commands": ["/usr/local/bin/subl"]
77 | }
78 | ],
79 | }
80 |
--------------------------------------------------------------------------------
/pre-push:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | # run from root of repo: `cd .git/hooks && ln -s -f ../../pre-push`
4 |
5 | isort --check .
6 | black --check .
7 | flake8 .
8 | pyright
9 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.black]
2 | line-length = 120
3 | target-version = ['py38']
4 |
--------------------------------------------------------------------------------
/pyrightconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["open_url.py", "url.py"],
3 | "useLibraryCodeForTypes": true,
4 | "reportOptionalSubscript": "error",
5 | "reportOptionalMemberAccess": "error",
6 | "reportOptionalCall": "error",
7 | "reportOptionalIterable": "error",
8 | "reportOptionalContextManager": "error",
9 | "reportOptionalOperand": "error",
10 | "strictListInference": true,
11 | "strictDictionaryInference": true,
12 | "typeCheckingMode": "basic",
13 | "reportMissingImports": true,
14 | "reportUnnecessaryCast": "warning",
15 | "reportUnnecessaryComparison": "error",
16 | "reportConstantRedefinition": "error",
17 | "reportUnnecessaryTypeIgnoreComment": "warning"
18 | }
19 |
--------------------------------------------------------------------------------
/url.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | # list of known TLDs, e.g. "com"
4 | domains = "aaa|aarp|abb|abbott|abbvie|abogado|abudhabi|ac|academy|accenture|accountant|accountants|aco|active|actor|ad|adac|ads|adult|ae|aeg|aero|af|afl|ag|agakhan|agency|ai|aig|airforce|airtel|akdn|al|alibaba|alipay|allfinanz|ally|alsace|am|amica|amsterdam|analytics|android|anquan|ao|apartments|app|apple|aq|aquarelle|ar|aramco|archi|army|arpa|arte|as|asia|associates|at|attorney|au|auction|audi|audio|author|auto|autos|avianca|aw|aws|ax|axa|az|azure|ba|baby|baidu|band|bank|bar|barcelona|barclaycard|barclays|barefoot|bargains|bauhaus|bayern|bb|bbc|bbva|bcg|bcn|bd|be|beats|beer|bentley|berlin|best|bet|bf|bg|bh|bharti|bi|bible|bid|bike|bing|bingo|bio|biz|bj|black|blackfriday|bloomberg|blue|bm|bms|bmw|bn|bnl|bnpparibas|bo|boats|boehringer|bom|bond|boo|book|boots|bosch|bostik|bot|boutique|br|bradesco|bridgestone|broadway|broker|brother|brussels|bs|bt|budapest|bugatti|build|builders|business|buy|buzz|bv|bw|by|bz|bzh|ca|cab|cafe|cal|call|camera|camp|cancerresearch|canon|capetown|capital|car|caravan|cards|care|career|careers|cars|cartier|casa|cash|casino|cat|catering|cba|cbn|cc|cd|ceb|center|ceo|cern|cf|cfa|cfd|cg|ch|chanel|channel|chase|chat|cheap|chloe|christmas|chrome|church|ci|cipriani|circle|cisco|citic|city|cityeats|ck|cl|claims|cleaning|click|clinic|clinique|clothing|cloud|club|clubmed|cm|cn|co|coach|codes|coffee|college|cologne|com|commbank|community|company|compare|computer|comsec|condos|construction|consulting|contact|contractors|cooking|cool|coop|corsica|country|coupon|coupons|courses|cr|credit|creditcard|creditunion|cricket|crown|crs|cruises|csc|cu|cuisinella|cv|cw|cx|cy|cymru|cyou|cz|dabur|dad|dance|date|dating|datsun|day|dclk|dds|de|dealer|deals|degree|delivery|dell|deloitte|delta|democrat|dental|dentist|desi|design|dev|diamonds|diet|digital|direct|directory|discount|dj|dk|dm|dnp|do|docs|dog|doha|domains|download|drive|dubai|durban|dvag|dz|earth|eat|ec|edeka|edu|education|ee|eg|email|emerck|energy|engineer|engineering|enterprises|epson|equipment|er|erni|es|esq|estate|et|eu|eurovision|eus|events|everbank|exchange|expert|exposed|express|extraspace|fage|fail|fairwinds|faith|family|fan|fans|farm|fashion|fast|feedback|ferrero|fi|film|final|finance|financial|firestone|firmdale|fish|fishing|fit|fitness|fj|fk|flickr|flights|flir|florist|flowers|flsmidth|fly|fm|fo|foo|football|ford|forex|forsale|forum|foundation|fox|fr|fresenius|frl|frogans|frontier|ftr|fund|furniture|futbol|fyi|ga|gal|gallery|gallo|gallup|game|garden|gb|gbiz|gd|gdn|ge|gea|gent|genting|gf|gg|ggee|gh|gi|gift|gifts|gives|giving|gl|glass|gle|global|globo|gm|gmail|gmbh|gmo|gmx|gn|gold|goldpoint|golf|goo|goog|google|gop|got|gov|gp|gq|gr|grainger|graphics|gratis|green|gripe|group|gs|gt|gu|guardian|gucci|guge|guide|guitars|guru|gw|gy|hamburg|hangout|haus|hdfcbank|health|healthcare|help|helsinki|here|hermes|hiphop|hitachi|hiv|hk|hkt|hm|hn|hockey|holdings|holiday|homedepot|homes|honda|horse|host|hosting|hoteles|hotmail|house|how|hr|hsbc|ht|htc|hu|hyundai|ibm|icbc|ice|icu|id|ie|ifm|iinet|il|im|imamat|immo|immobilien|in|industries|infiniti|info|ing|ink|institute|insurance|insure|int|international|investments|io|ipiranga|iq|ir|irish|is|iselect|ismaili|ist|istanbul|it|itau|iwc|jaguar|java|jcb|jcp|je|jetzt|jewelry|jlc|jll|jm|jmp|jnj|jo|jobs|joburg|jot|joy|jp|jpmorgan|jprs|juegos|kaufen|kddi|ke|kerryhotels|kerrylogistics|kerryproperties|kfh|kg|kh|ki|kia|kim|kinder|kitchen|kiwi|km|kn|koeln|komatsu|kp|kpmg|kpn|kr|krd|kred|kuokgroup|kw|ky|kyoto|kz|la|lacaixa|lamborghini|lamer|lancaster|land|landrover|lanxess|lasalle|lat|latrobe|law|lawyer|lb|lc|lds|lease|leclerc|legal|lexus|lgbt|li|liaison|lidl|life|lifeinsurance|lifestyle|lighting|like|limited|limo|lincoln|linde|link|lipsy|live|living|lixil|lk|loan|loans|locus|lol|london|lotte|lotto|love|lr|ls|lt|ltd|ltda|lu|lupin|luxe|luxury|lv|ly|ma|madrid|maif|maison|makeup|man|management|mango|market|marketing|markets|marriott|mba|mc|md|me|med|media|meet|melbourne|meme|memorial|men|menu|meo|metlife|mg|mh|miami|microsoft|mil|mini|mk|ml|mls|mm|mma|mn|mo|mobi|mobily|moda|moe|moi|mom|monash|money|montblanc|mormon|mortgage|moscow|motorcycles|mov|movie|movistar|mp|mq|mr|ms|mt|mtn|mtpc|mtr|mu|museum|mutual|mutuelle|mv|mw|mx|my|mz|na|nadex|nagoya|name|natura|navy|nc|ne|nec|net|netbank|network|neustar|new|news|next|nextdirect|nexus|nf|ng|ngo|nhk|ni|nico|nikon|ninja|nissan|nissay|nl|no|nokia|northwesternmutual|norton|nowruz|nowtv|np|nr|nra|nrw|ntt|nu|nyc|nz|obi|office|okinawa|olayan|olayangroup|om|omega|one|ong|onl|online|ooo|oracle|orange|org|organic|origins|osaka|otsuka|ovh|pa|page|pamperedchef|panerai|paris|pars|partners|parts|party|passagens|pccw|pe|pet|pf|pg|ph|pharmacy|philips|photo|photography|photos|physio|piaget|pics|pictet|pictures|pid|pin|ping|pink|pizza|pk|pl|place|play|playstation|plumbing|plus|pm|pn|pohl|poker|porn|post|pr|praxi|press|pro|prod|productions|prof|progressive|promo|properties|property|protection|ps|pt|pub|pw|pwc|py|qa|qpon|quebec|quest|racing|re|read|realtor|realty|recipes|red|redstone|redumbrella|rehab|reise|reisen|reit|ren|rent|rentals|repair|report|republican|rest|restaurant|review|reviews|rexroth|rich|richardli|ricoh|rio|rip|ro|rocher|rocks|rodeo|room|rs|rsvp|ru|ruhr|run|rw|rwe|ryukyu|sa|saarland|safe|safety|sakura|sale|salon|samsung|sandvik|sandvikcoromant|sanofi|sap|sapo|sarl|sas|saxo|sb|sbi|sbs|sc|sca|scb|schaeffler|schmidt|scholarships|school|schule|schwarz|science|scor|scot|sd|se|seat|security|seek|select|sener|services|seven|sew|sex|sexy|sfr|sg|sh|sharp|shaw|shell|shia|shiksha|shoes|shouji|show|shriram|si|sina|singles|site|sj|sk|ski|skin|sky|skype|sl|sm|smile|sn|sncf|so|soccer|social|softbank|software|sohu|solar|solutions|song|sony|soy|space|spiegel|spot|spreadbetting|sr|srl|st|stada|star|starhub|statebank|statefarm|statoil|stc|stcgroup|stockholm|storage|store|stream|studio|study|style|su|sucks|supplies|supply|support|surf|surgery|suzuki|sv|swatch|swiss|sx|sy|sydney|symantec|systems|sz|tab|taipei|talk|taobao|tatamotors|tatar|tattoo|tax|taxi|tc|tci|td|team|tech|technology|tel|telecity|telefonica|temasek|tennis|teva|tf|tg|th|thd|theater|theatre|tickets|tienda|tiffany|tips|tires|tirol|tj|tk|tl|tm|tmall|tn|to|today|tokyo|tools|top|toray|toshiba|total|tours|town|toyota|toys|tr|trade|trading|training|travel|travelers|travelersinsurance|trust|trv|tt|tube|tui|tunes|tushu|tv|tvs|tw|tz|ua|ubs|ug|uk|unicom|university|uno|uol|us|uy|uz|va|vacations|vana|vc|ve|vegas|ventures|verisign|versicherung|vet|vg|vi|viajes|video|vig|viking|villas|vin|vip|virgin|vision|vista|vistaprint|viva|vlaanderen|vn|vodka|volkswagen|vote|voting|voto|voyage|vu|vuelos|wales|walter|wang|wanggou|warman|watch|watches|weather|weatherchannel|webcam|weber|website|wed|wedding|weibo|weir|wf|whoswho|wien|wiki|williamhill|win|windows|wine|wme|wolterskluwer|work|works|world|ws|wtc|wtf|xbox|xerox|xihuan|xin|xn--11b4c3d|xn--1ck2e1b|xn--1qqw23a|xn--30rr7y|xn--3bst00m|xn--3ds443g|xn--3e0b707e|xn--3pxu8k|xn--42c2d9a|xn--45brj9c|xn--45q11c|xn--4gbrim|xn--55qw42g|xn--55qx5d|xn--5tzm5g|xn--6frz82g|xn--6qq986b3xl|xn--80adxhks|xn--80ao21a|xn--80asehdb|xn--80aswg|xn--8y0a063a|xn--90a3ac|xn--90ais|xn--9dbq2a|xn--9et52u|xn--9krt00a|xn--b4w605ferd|xn--bck1b9a5dre4c|xn--c1avg|xn--c2br7g|xn--cck2b3b|xn--cg4bki|xn--clchc0ea0b2g2a9gcd|xn--czr694b|xn--czrs0t|xn--czru2d|xn--d1acj3b|xn--d1alf|xn--e1a4c|xn--eckvdtc9d|xn--efvy88h|xn--estv75g|xn--fct429k|xn--fhbei|xn--fiq228c5hs|xn--fiq64b|xn--fiqs8s|xn--fiqz9s|xn--fjq720a|xn--flw351e|xn--fpcrj9c3d|xn--fzc2c9e2c|xn--fzys8d69uvgm|xn--g2xx48c|xn--gckr3f0f|xn--gecrj9c|xn--h2brj9c|xn--hxt814e|xn--i1b6b1a6a2e|xn--imr513n|xn--io0a7i|xn--j1aef|xn--j1amh|xn--j6w193g|xn--jlq61u9w7b|xn--jvr189m|xn--kcrx77d1x4a|xn--kprw13d|xn--kpry57d|xn--kpu716f|xn--kput3i|xn--l1acc|xn--lgbbat1ad8j|xn--mgb9awbf|xn--mgba3a3ejt|xn--mgba3a4f16a|xn--mgba7c0bbn0a|xn--mgbaam7a8h|xn--mgbab2bd|xn--mgbayh7gpa|xn--mgbb9fbpob|xn--mgbbh1a71e|xn--mgbc0a9azcg|xn--mgbca7dzdo|xn--mgberp4a5d4ar|xn--mgbpl2fh|xn--mgbt3dhd|xn--mgbtx2b|xn--mgbx4cd0ab|xn--mix891f|xn--mk1bu44c|xn--mxtq1m|xn--ngbc5azd|xn--ngbe9e0a|xn--node|xn--nqv7f|xn--nqv7fs00ema|xn--nyqy26a|xn--o3cw4h|xn--ogbpf8fl|xn--p1acf|xn--p1ai|xn--pbt977c|xn--pgbs0dh|xn--pssy2u|xn--q9jyb4c|xn--qcka1pmc|xn--qxam|xn--rhqv96g|xn--rovu88b|xn--s9brj9c|xn--ses554g|xn--t60b56a|xn--tckwe|xn--unup4y|xn--vermgensberater-ctb|xn--vermgensberatung-pwb|xn--vhquv|xn--vuq861b|xn--w4r85el8fhu5dnra|xn--w4rs40l|xn--wgbh1c|xn--wgbl6a|xn--xhq521b|xn--xkc2al3hye2a|xn--xkc2dl3a5ee0h|xn--y9a3aq|xn--yfro4i67o|xn--ygbi2ammx|xn--zfr164b|xperia|xxx|xyz|yachts|yahoo|yamaxun|yandex|ye|yodobashi|yoga|yokohama|you|youtube|yt|yun|za|zara|zero|zip|zm|zone|zuerich|zw" # noqa
5 |
6 |
7 | def is_url(path: str) -> bool:
8 | return bool(re.search(r"\w[^\s]*\.(?:%s)(/[^\s]*)?\Z" % domains, path, re.IGNORECASE))
9 |
--------------------------------------------------------------------------------