diff --git a/content/posts/regex-based-url-opener.adoc b/content/posts/regex-based-url-opener.adoc new file mode 100644 index 0000000..e5affcb --- /dev/null +++ b/content/posts/regex-based-url-opener.adoc @@ -0,0 +1,161 @@ +--- +title: "Open URLs based on regular expressions" +slug: "regex-based-url-opener" +description: null +date: 2022-04-18T17:16:08+02:00 +type: posts +draft: true +tags: +- WWW +- Zsh +toc: true +--- + +:source-highlighter: pygments +:idprefix: +:experimental: true +:toc: +:toclevels: 2 + +:url-urlhandler: https://schlomp.space/tastytea/dotfiles/src/branch/main/.local/bin/urlhandler +:url-functions: https://schlomp.space/tastytea/dotfiles/src/branch/main/.config/zsh/functions +:url-zshoptions: https://zsh.sourceforge.io/Doc/Release/Options.html +:url-zshfunctions: https://zsh.sourceforge.io/Doc/Release/Functions.html +:url-desktop-spec: https://specifications.freedesktop.org/desktop-entry-spec/latest/ + +:abbr-pcre: pass:[PCRE] + +One of the shell scripts that bring me the most joy is my URL opener. I have it +set as my default browser, so whenever I click a hyperlink somewhere it gets +called and decides if the URL should be opened with Firefox or with another +program. mpv for video files and YouTube URLs, a download-and-launch-viewer +function for pictures and so on. + +== The script + +I have abbreviated the script here a bit for better readability, the full +version is available link:{url-urlhandler}[in my dotfiles repo]. + +[source,shell] +-------------------------------------------------------------------------------- +#!/usr/bin/env zsh +# Open URLs based on perl compatible regular expressions. Several commands can +# be specified with ; as separator. If the URL doesn't match anything or the +# commands are not found, use ${default_cmd}. + +setopt ERR_RETURN NO_UNSET PIPE_FAIL + +if [[ ! -v 1 ]]; then + print -u 2 "usage: ${0} …" + return 1 +fi +local -a urls=(${@}) + +autoload -U readwwwlog openwwwimg +zmodload zsh/pcre + +local default_cmd="firefox" +-------------------------------------------------------------------------------- + +First, we set some essential options. `ERR_RETURN` stops execution when a +command has a non-zero exit status, `NO_UNSET` treats non-existing variables as +an error and `PIPE_FAIL` sets `${?}` to non-zero if any command in a pipeline +fails. You can read more about them in the *zshoptions* man-page or in the +link:{url-zshoptions}[online documentation]. + +Next we check if `${1}` is set (we have at least 1 URL) and assign all arguments +to the array `${urls}`. Then we autoload some function link:#helper_functions[we +later use], load the {abbr-pcre} module and set the default browser. + +[source,shell] +-------------------------------------------------------------------------------- +local mpv="mpv --force-window=yes" + +local -A assignments=( + '\.(mp4|m4v|mkv|avi|webm|flv|xvid|ogv|theora|mov|wmv)$' "${mpv};vlc" + '\.(ogg|flac|opus|m4a|wav|mp3|mid|aac|wma)$' "${mpv};vlc" + '^https://media\.ccc\.de/v/[^/]*$' "${mpv}" + '^https?://(www\.)?youtu(\.be|be\.com)/' "${mpv}" + '^(gemini|gopher)://' "kristall" + '\.(log|txt)$' "readwwwlog" + '^https?://(www\.)?(dpaste|pastebin|irccloud)\.com/' "readwwwlog" + '\.(webm|png|jpe?g|gif|svg|bmp|xcf)$' "openwwwimg" +) +-------------------------------------------------------------------------------- + +Now we define the associative array `${assignments}` with the regular expression +on the left and the command(s) to run on the right. The first command that +exists will be run. + +[source,shell] +-------------------------------------------------------------------------------- +local selected_cmd=${default_cmd} +for regex cmds in ${(kv)assignments}; do + if [[ ${urls[1]} -pcre-match ${regex} ]]; then + for cmd in ${(s/;/)cmds}; do + if type ${cmd%% *} >& -; then + selected_cmd=${cmd} + break + fi + done + break + fi +done + +${(@s/ /)selected_cmd} ${urls} +-------------------------------------------------------------------------------- + +This loops through all regular expressions and compares it against the +first URL (we naively assume that all supplied URLs are of the same type 😊). If +it matches, the first command that exists is run with the URL. If nothing +matches, the default browser we set earlier is used. + +[NOTE] +Because commands can contain options, we split the command-strings at spaces in +the existence check and when we run the command. This has the side-effect that +commands can not contain spaces. + +== Helper functions + +The functions `readwwwlog` and `openwwwimage` are available in the +link:{url-functions}[Zsh functions directory] of my dotfiles repo. Put them +anywhere in your `${fpath}`. Learn more from the *zshmisc* man-page or the +link:{url-zshfunctions}[online documentation]. Or you could just put them inside +the script, like this for example: + +[source,shell] +-------------------------------------------------------------------------------- +function openwwwimg() { + local file="$(mktemp --suffix='.openwwwimg')" + curl --silent --location --output ${file} ${url} + if [[ ${?} -eq 0 ]]; then + viewnior ${file} + else + firefox ${url} + fi + rm -f ${file} +} +-------------------------------------------------------------------------------- + +Don't forget to remove the autoloads if you put them directly in the script. + +== Setting the script as default browser + +To set the default browser in Linux, you need to have a `.desktop` file +(link:{url-desktop-spec}[specification]). + +[source,toml] +-------------------------------------------------------------------------------- +[Desktop Entry] +Type=Application +Version=1.0 +Name=urlhandler +Comment=Open URLs with different commands +TryExec=urlhandler +Exec=urlhandler %u +Terminal=false +-------------------------------------------------------------------------------- + +Save this in `~/.local/share/applications/urlhandler.desktop` and run +`xdg-settings set default-web-browser urlhandler.desktop`. Congratulations, you +are ready to go! 🥳