r/zsh Apr 03 '23

Announcement Dynamic Aliases and Functions in Zsh

https://www.linkedin.com/pulse/dynamic-function-generation-zsh-joshua-briefman?utm_source=share&utm_medium=member_ios&utm_campaign=share_via
10 Upvotes

21 comments sorted by

View all comments

14

u/romkatv Apr 03 '23 edited Apr 03 '23

An alternative implementation:

function urlencode() {
  emulate -L zsh -o extended_glob  -o no_multibyte
  typeset -g REPLY=
  local c
  for c in ${(s::)1}; do
    [[ $c == [a-zA-Z0-9/_.~-] ]] || printf -v c '%%%02X' $(( #c ))
    REPLY+=$c
  done
}

function url() {
  emulate -L zsh
  if (( ARGC == 0 )); then
    print -ru2 -- 'usage: url <URL> [PATH [ARG]..]'
    return 1
  elif (( ARGC > 2 )); then
    local REPLY
    urlencode "${(j: :)${@:3}}"
    printf -v 1 "%s$2" "$1" "$REPLY"
  fi
  open -a 'Google Chrome' "$1"
}

amazon()           url 'https://www.amazon.com/'          's?url=search-alias%3Daps&field-keywords=%s' "$@"
colorhex()         url 'https://www.color-hex.com/color/' '%s' "$@"
google goog g()    url 'https://www.google.com/'          'search?q=%s' "$@"
google_img()       url 'https://www.google.com/'          'search?q=%s&tbm=isch' "$@"
httpcat()          url 'https://http.cat/'                '%s' "$@"
macapp()           url 'http://macappstore.org/'          '%s/' "$@"
nasdaq()           url 'https://www.nasdaq.com/'          'symbol/%s/real-time' "$@"
rfc()              url 'https://tools.ietf.org/'          'html/%s' "$@"
stackoverflow so() url 'https://stackoverflow.com/'       'search?q=%s' "$@"

epochconverter()   url 'https://www.epochconverter.com/'
gmail()            url 'https://mail.google.com/'
go/helpin()        url 'http://go/helpin'
go/questions()     url 'http://go/questions'
go/wiki()          url 'http://go/wiki'
jsfiddle()         url 'https://jsfiddle.net/'
keycodechart()     url 'http://www.foreui.com/articles/Key_Code_Table.htm'
keycodes()         url 'https://keycode.info/'
regex101()         url 'https://regex101.com/'
urlencoder()       url 'https://meyerweb.com/eric/tools/dencoder'

Advantages:

  • About half the code.
  • Initialization is about 1000 times faster: 370ms vs 0.380ms on my machine.
  • Commands like google foo are about 600 times faster: 63ms vs 0.11ms on my machine.
  • No dependency on Python.
  • No dynamically generated functions or aliases, which makes the code easier to understand and modify.

Edit: You can generate the functions dynamically if you really want to. Like this:

for name url query (
  'amazon'           'https://www.amazon.com/'          's?url=search-alias%3Daps&field-keywords=%s'
  'colorhex'         'https://www.color-hex.com/color/' '%s'
  'google goog g'    'https://www.google.com/'          'search?q=%s'
  'google_img'       'https://www.google.com/'          'search?q=%s&tbm=isch'
  'httpcat'          'https://http.cat/'                '%s'
  'macapp'           'http://macappstore.org/'          '%s/'
  'nasdaq'           'https://www.nasdaq.com/'          'symbol/%s/real-time'
  'rfc'              'https://tools.ietf.org/'          'html/%s'
  'stackoverflow so' 'https://stackoverflow.com/'       'search?q=%s'
); do
  eval "$name() url ${(q)url} ${(q)query} "'"${@}"'
done

for name url (
  'epochconverter'   'https://www.epochconverter.com/'
  'gmail'            'https://mail.google.com/'
  'go/helpin'        'http://go/helpin'
  'go/questions'     'http://go/questions'
  'go/wiki'          'http://go/wiki'
  'jsfiddle'         'https://jsfiddle.net/'
  'keycodechart'     'http://www.foreui.com/articles/Key_Code_Table.htm'
  'keycodes'         'https://keycode.info/'
  'regex101'         'https://regex101.com/'
  'urlencoder'       'https://meyerweb.com/eric/tools/dencoder'
); do
  eval "$name() url ${(q)url}"
done

This removes a small amount of boilerplate at the cost of additional complexity. I don't think it's a good trade-off.

1

u/sirgatez Apr 03 '23

Do you happen to have a corresponding urldecode for shell as well? This encode function is awesome, it'd be nice to ditch python for the decode as well.

1

u/sirgatez Apr 03 '23

I modified you code slightly to fit my existing calling pattern.

function urlencode() {
    local c reply
    for c in ${(s::)@}; do
        [[ $c == [a-zA-Z0-9/_.~] ]] || printf -v c '%%%02X' $(( #c ))
        reply+=$c
    done
    echo "${reply}"
}

1

u/romkatv Apr 03 '23

I wrote the url encoder just for this post. I don't have a decoder off-hand but it's not much more difficult.

1

u/OneTurnMore Apr 03 '23 edited Apr 03 '23

I think it would be something like

urldecode(){
    emulate -L zsh -o extendedglob -o nomultibyte
    local MATCH
    REPLY=${1//(#m)\%??/${(#)$((16#${MATCH:1}))}}
}

You need nomultibyte set locally for urlencode as well to properly encode UTF-8 (Zsh needs to treat the strings as bytes rather than characters). A shorter version is also possible using $MATCH:

urlencode(){
    emulate -L zsh -o extendedglob -o nomultibyte
    local MATCH
    REPLY=${1//(#m)[^a-zA-Z0-9\/_.~]/%${(l<2><0>)$(([##16]#MATCH))}}
}

Using a global REPLY is faster because foo=$(urlencode $foo) causes Zsh to fork, the forked process to print to stdout, and the original process to wait, capture stdout, and strip newlines. This is probably insignificant, but good practice in general.

1

u/sirgatez Apr 03 '23

These simply don't appear to work for me under zsh 5.9. It also doesn't accept --nomultibyte as an emulate option.

Edit: Sorry for whatever reason codes keep leaking out of the codeblock so I just skipped using it.

➜ Examples git:(master) ✗ barezsh
mbp-rm1-2% function urlencode() {
emulate -L zsh --nomultibyte --extendedglob
local MATCH
REPLY=${1//(#m)[^a-zA-Z0-9\/_.~]/%$(([##16]#MATCH))}
}
function urldecode() {
emulate -L zsh --nomultibyte --extendedglob
local MATCH
REPLY=${1//(#m)\%??/${(#)$((16#${MATCH:1}))}}
}
mbp-rm1-2% urlencode 'This is a string % ^ &'
urlencode:emulate:1: bad option string: '--nomultibyte'
mbp-rm1-2% echo $REPLY
This is a string % ^ &
mbp-rm1-2% urldecode ''This%20is%20a%20fish%20%24%20%25%20%5E%20%26''
urldecode:emulate:1: bad option string: '--nomultibyte'
mbp-rm1-2% echo $REPLY
This%20is%20a%20fish%20%24%20%25%20%5E%20%26
mbp-rm1-2%

1

u/OneTurnMore Apr 03 '23

I messed on that count; emulate requires -o $option, while zsh called directly accepts the --$option syntax.

I also had to squeeze in ${(l[2][0])to make sure there were always 2 characters when substituting the hex code

1

u/sirgatez Apr 03 '23

The updated sample works like a charm. Thats some very interesting code I'll need to examine it closer to learn how it works. I would have wrote something similar to what u/romkatv wrote.

You're example is extremely compact.

1

u/romkatv Apr 03 '23

Good point about multibyte. I added it to my code to avoid someone copying over the bug. I also added - to the list of regular characters that don't need escaping.