-
Notifications
You must be signed in to change notification settings - Fork 294
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proper Variable Expansion #56
Comments
Great idea! I can certainly see how this can be useful. We probably want to follow the Open Group standard rather than the Bash one. Is there a Python builtin similar to |
I was meaning to link the Open Group standard, but I was at work (office) and had to find it using my phone, so I just got the one I found first. Unfortunately I couldn't find a Python builtin that achieve this... Or a package (had a quick look through pypi), so we'll probably have to write one ourselves (maybe make it into a package for others to use?) The standard is fairly well defined. I see four possible solutions:
Technically, However, a (VERY) minimalistic implementation of basic expansion using it (no error checking etc) is effectively 10 lines of code. Fairly good question on how to approach this. |
I'd prefer to write an implementation from scratch in pure Python. For reference, here's the implementation of # Expand paths containing shell variable substitutions.
# This expands the forms $variable and ${variable} only.
# Non-existent variables are left unchanged.
_varprog = None
_varprogb = None
def expandvars(path):
"""Expand shell variables of form $var and ${var}. Unknown variables
are left unchanged."""
global _varprog, _varprogb
if isinstance(path, bytes):
if b'$' not in path:
return path
if not _varprogb:
import re
_varprogb = re.compile(br'\$(\w+|\{[^}]*\})', re.ASCII)
search = _varprogb.search
start = b'{'
end = b'}'
environ = getattr(os, 'environb', None)
else:
if '$' not in path:
return path
if not _varprog:
import re
_varprog = re.compile(r'\$(\w+|\{[^}]*\})', re.ASCII)
search = _varprog.search
start = '{'
end = '}'
environ = os.environ
i = 0
while True:
m = search(path, i)
if not m:
break
i, j = m.span(0)
name = m.group(1)
if name.startswith(start) and name.endswith(end):
name = name[1:-1]
try:
if environ is None:
value = os.fsencode(os.environ[os.fsdecode(name)])
else:
value = environ[name]
except KeyError:
i = j
else:
tail = path[j:]
path = path[:i] + value
i = len(path)
path += tail
return path So it's not way too complicated, but it's not super simple either. |
Maybe we can wrap around this? |
Yeah, that could save some effort. |
The Open Group spec specifies different cases for when a parameter is set, but null (e.g |
Current state: I got (as far as I can tell, for now) basic detection going (including depth-first searches so we expand the innermost things first, to avoid collisions). What I'm going to do (for now) is I'll make null variants work the same as the non-null ones (e.g Has support for: |
Did some testing with various shells, and there seems to be disagreement on what constitutes a "Null Parameter". The script I used to test can be found here.
All the shells except zsh had the same output. Any insight? Could default to python's detection (which is consistent with the other shells, |
Nice! You're also supporting command substitution? That's cool. Depth-first searches? Does the standard allow for nested expansion (I don't know)? Defaulting to Python's detection (using what's in |
The standard does not seem to require it, but every single implementation I see does. Try this, for example Command substitution is fairly trivial to achieve, but it might cause side effects if it directly replaces Not entirely sure what to do if the variable is false: either keep the value (e.g |
Oh I see. For our purposes, it may be okay to skip recursive substitution if it's more effort. What's the issue with command substitution and variable expansion? One uses Btw, we might want to skip implementing |
I already have recursive substitution, it's very easy to implement. The issue with Not entirely sure how we're dependent on execution order... the typical use case is for stuff like XDG_CONFIG_HOME, where you go ${XDG_CONFIG_HOME:=$HOME/.config}, that way if it is set, use it, and if it isn't, default to the spec. |
I don't think we need to worry about what's in a That being said, I don't know if we want to support Using default values is fine, we just shouldn't assign default values. |
If the standard specifies |
Right. But if there's something like this for example: (suppose - link:
"a": "~/${parameter:=[a]}"
"b": "~/${parameter:=[b]}" Then the results will be dependent on the order in which these are executed. And we don't guarantee a specific order of execution. |
This use case makes no sense, however. Essentially, I don't see a reason not to implement it, especially considering I already have support for it. (I'm planning to clean up a bit and upload an initial version for sanity testing before Monday). |
Okay, sure. I guess there's no harm in having it implemented. |
I still want to implement %, %%, # and ##, but I need to understand them better first... not entirely sure how I'd make them. This is because after some thought, I realized I want this to be where my zshrc links: Current status: here. I'm taking a small break (some personal life issues arose), so please accumulate comments and suggestions here until then :) |
Whoa progress looks good! Yeah, take your time, best of luck with everything that's going on. |
If you could test / read / give out comments (RFC style), that'd be really appreciated :) The short version of how it functions (to save you time if you do choose to go through the code):
the |
Ok, I'll take a look soon. Things are a bit crazy right now with projects and finals at university, so it may be a week or two… |
Well, this would be a replacement for posixpath. |
👍 I just added that comment to make sure that we don't forget :) |
FYI there would definitely be more projects that could use a way to use a POSIX compatible variable substitution, most of the time the main reason is being able to define a default value. |
Possible use case So I have some systems that I would preferr alternative configs. likewise ignore, use .tmux.conf every where expect on A B C machines. |
I think it's too much effort to get this right / enough of an edge case that it's not worth the effort. For the original task:
There's a workaround by adding |
Currently, dotbot uses os.path.expandvars.
However, in some cases, it may be desireable to follow proper shell parameter substitution. (see: the related manual)
Here is an example usage: I want my zshrc to work. Some systems may define
$ZDOTDIR
to be$XDG_DATA_HOME/zsh
, some may leave it undefined.The typical solution would be to use
${ZDOTDIR:-$HOME}/.zshrc
as the install location. However, without parameter substitution, there is no way to achieve this.The text was updated successfully, but these errors were encountered: