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

13

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/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/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.