-
-
Save princed/8ed52eeb8d665b00f6f8330132863157 to your computer and use it in GitHub Desktop.
Python script that converts Nix's profile bash script to Fish
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import re, sys, os | |
""" | |
Converts the Nix profile SH script to a Fish-compatible profile using a | |
simple line-by-line replace algorithm. | |
""" | |
# Regular expressions for things we need to rewrite | |
RE_TEST = re.compile("\[([^\]]+)\]") | |
RE_EXPORT = re.compile("^(\s*)export ([A-Z_]+)\s*=(.+)$") | |
RE_SET = re.compile("^(\s*)([A-Za-z_]+)\s*=(.+)$") | |
RE_IF_NOT = re.compile("(\s*)if\s+!\s+(.*)$") | |
RE_FI = re.compile("(^\s*|\s+)fi(\s+|\s*$)") | |
RE_UNSET = re.compile("^(\s*)unset(\s*)(.+)$") | |
RE_BRACKET = re.compile("\${([A-Z_]+)(:-)?\}") | |
RE_WHITESPACE = re.compile("^[\s\n]+") | |
# Simple word-for-word rewrites | |
REWRITE = { | |
"elif " : "else if ", | |
"! test" : "test !", | |
"; then" : ";", | |
" && " : "; and ", | |
"$(" : "(", | |
} | |
def unwrap( text ): | |
"""Takes a line like `"abc:def"` and returns `abc def`. This is | |
useful for converting sh-like path lists to fish path lists.""" | |
text = text.strip() | |
if len(text) > 2 and text[0] == text[-1] and text[0] == '"': | |
return " ".join(text[1:-1].split(":")) | |
else: | |
return text | |
def re_replace( regexp, text, functor ): | |
"""Replaces all matching instances of `regexp` in `text` by passing the | |
match to the given `functor`.""" | |
r = [] | |
o = 0 | |
for m in regexp.finditer(text): | |
r.append(text[o:m.start()]) | |
r.append(functor(m)) | |
o = m.end() | |
r.append(text[o:]) | |
return "".join(r) | |
def process_line( line ): | |
"""Processes the given line of Shell code, applying both the regexes | |
and word substitutions.""" | |
nix_path = "${NIX_PATH:+$NIX_PATH:}" | |
if nix_path in line: | |
# Here we have a special case which is quite annoying, where a Bash | |
# substitution is used with a default value. | |
line = "\n".join(( | |
'if test -z "$NIX_PATH";', | |
' ' + process_line(line.replace(nix_path, "")), | |
'else', | |
' ' + process_line(line.replace(nix_path, "$NIX_PATH:")), | |
'end', | |
)) | |
return line | |
# Fish doesn't allow using return outside of a function | |
if '"${__ETC_PROFILE_NIX_SOURCED:-}"' in line: | |
line = line.replace('return', 'exit 0') | |
# Fish is a little bit annoying with shell expressions in strings. | |
# "(id -u)" is not like "$(id -u)" in bash, but should be ""(id -u)"" | |
l = RE_WHITESPACE.sub("", line) | |
if l.startswith("echo"): | |
line = line.replace("$(", '"(').replace(")", ')"').replace('""','') | |
elif l.startswith("if"): | |
line = line.replace('"$(', "(").replace(')"', ")") | |
elif l.startswith("__savedpath="): | |
# This is a special handling of the initial path saving. Fish complains | |
# about non-existing entries in PATH, so we need to filter them first. | |
return " for d in $PATH; if test -e $d; set __savedpath $d $__savedpath; end; end;" | |
m = RE_IF_NOT.match(line) | |
if m: | |
pre = m.group(1) | |
cmd = m.group(2) | |
return "\n".join(( | |
"{0}{1}".format(pre, process_line(cmd)), | |
"{0}if test ! $status -eq 0;".format(pre) | |
)) | |
line = re_replace(RE_TEST, line, lambda m:"test{0}".format(m.group(1))) | |
line = re_replace(RE_EXPORT, line, lambda m:"{0}set -xg {1} {2}".format(m.group(1), m.group(2), unwrap(m.group(3)))) | |
line = re_replace(RE_SET, line, lambda m:"{0}set -g {1} {2}".format(m.group(1), m.group(2), unwrap(m.group(3)))) | |
line = re_replace(RE_UNSET, line, lambda m:"{0}{1}".format(m.group(1), "; ".join("set -e " + _ for _ in m.group(3).split()))) | |
line = re_replace(RE_FI, line, lambda m:"{0}end{1}".format(m.group(1),m.group(2))) | |
line = re_replace(RE_BRACKET,line, lambda m:"${0}".format(m.group(1))) | |
for s,d in REWRITE.items(): line = line.replace(s,d) | |
return line | |
def process( text, origin ): | |
"""Processes the given text.""" | |
res = [ | |
"#!/usr/bin/env fish", | |
"# --8<-- [This file is automatically generated from {0}, do not edit] ---".format(origin), | |
] | |
for line in text.split("\n"): | |
res.append(process_line(line)) | |
res.append("# --8<-- EOF --- vim: readonly\n") | |
return "\n".join(res) | |
if __name__ == "__main__": | |
src = "/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh" | |
dst = "~/.config/fish/nix-daemon.fish" | |
if not os.path.exists(os.path.expanduser(src)): | |
sys.stderr.write("Nix does not seem to be installed, expecting {0}".format(src)) | |
sys.exit(-1) | |
with open(os.path.expanduser(src), "rt") as f: | |
t = process(f.read(), src) | |
with open(os.path.expanduser(dst), "wt") as g: | |
g.write(t) | |
sys.stderr.write("To use Nix from Fish: source {0}".format(dst)) | |
# EOF |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Other than bash, fish complains about paths defined in $PATH that do not exist. You might want to either add those dirs or remove the entries from
~/.config/fish/nix-daemon.fish
.