Skip to content

Instantly share code, notes, and snippets.

@artizirk
Last active July 12, 2023 15:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save artizirk/4a3a9d98992e350ee0e0acb48c4c4fcc to your computer and use it in GitHub Desktop.
Save artizirk/4a3a9d98992e350ee0e0acb48c4c4fcc to your computer and use it in GitHub Desktop.
Parse python venv and virtualenv pyvenv.cfg with pure ZSH
#!/usr/bin/env zsh
# Parse python venv and virtualenv pyvenv.cfg with pure ZSH
#
# My usecase was to get the custom virtualenv prompt name from
# python virtualenvs created by poetry and virtualenv package.
# Builtin venv puts the prompt value also into environment variable
# called VIRTUAL_ENV_PROMPT that makes my life much easier.
# Virtualenv guys recommend to use pyvenv.cfg
# https://github.com/pypa/virtualenv/issues/2194
# https://github.com/pypa/virtualenv/issues/1968
# Also, even pyvenv itself does not always set the VIRTUAL_ENV_PROMPT
# https://github.com/python/cpython/issues/79509#issuecomment-1093805547
# The Bible: https://zsh.sourceforge.io/Doc/Release/Expansion.html
#set -x
function parse1 {
## parse all pyvenv.cfg keys into a associative array
# This kinda works
# All leading and trailing whitespace is trimmed from the keys
# Only parses the pyvenv.cfg if key and value are seperated by ` = `
# Does not remove extra ' and " from around the values
declare -A pyvenv
pyvenv=( ${(@f)${(F)${(MS)${(s: = :)"$(<$VIRTUAL_ENV/pyvenv.cfg)"}##[[:graph:]]*[[:graph:]]}}} )
# print all keys ver1
for k v ("${(@kv)pyvenv}") print -r -- "'$k' => '$v'"
# print all keys ver2
#for key val ("${(@kv)pyenv}"); do
# print "'$key' -> '$val'"
#done
# print all keys ver3
#print -acC2 "${(@kv)pyvenv}"
# print venv name/prompt value if any is found
print "the prompt is '${pyvenv[prompt]}'"
}
#function parse2 {
# ## parse single field from pyvenv.cfg into var using ZSH Parameter Expansion
# # broken because I don't know enough ZSH-fuu to limit the match between two new lines
# prompt=${(MS)"$(<$VIRTUAL_ENV/pyvenv.cfg)"#((#s)|$'\n')prompt?=?(*)$'\n'}
# print $prompt
# print ${match[1]}
#}
#function parse3 {
# ## parse single field from pyvenv.cfg into var using PCRE
# # broken because I couldn't figure out how to propperly match beginning and end of a line
#
# # the default `zsh/regex` just sucks, I wasted too much time on trying to get it to work
# # switching to `zsh/pcre` made everything work
# setopt re_match_pcre
# if [[ "$(<$VIRTUAL_ENV/pyvenv.cfg)" =~ [$'\n'^]prompt\\s*=\\s*[\'\"]?([\\w ]*)[\'\"]? ]]; then
# #print "match -> '$MATCH'"
# print "match1 -> '${match[1]}'"
# else
# print "no match :("
# fi
# }
#}
function parse4 {
## Parse venv prompt value from the pyvenv.cfg
emulate -L zsh
# Use a REAL regex engine
zmodload zsh/pcre
# Matches lines with following syntax
# prompt = 'cool prompt'
# prompt = "cool prompt"
# prompt = cool
# prompt=cool prompt
# heredoc is used here so that I don't have to
# manually escape the regex 🤮
read -r prompt_re <<-'EOF'
^prompt\s*=\s*['"]?(.+?)['"]?$
EOF
# -m turns on multiline support, this makes ^ and $ anchor work in multiline string
pcre_compile -m ${prompt_re}
# And now its supper easy to get the value
if pcre_match -- "$(<$VIRTUAL_ENV/pyvenv.cfg)"
then
print "match1 -> '${match[1]}'"
else
print "no match :("
fi
}
parse4
# Benchmark
#time (for (( i = 0; i < 10000; i += 1 )); do parse1; done) # on my machine 0,375 total
#time (for (( i = 0; i < 10000; i += 1 )); do parse4; done) # on my machine 0,858 total
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment