Skip to content

Instantly share code, notes, and snippets.

@sebastien
Last active May 31, 2021 02:52
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save sebastien/18a7eb71fdd34f6a0f825e69f9461d01 to your computer and use it in GitHub Desktop.
Save sebastien/18a7eb71fdd34f6a0f825e69f9461d01 to your computer and use it in GitHub Desktop.
Python script that converts Nix's profile bash script to Fish
#!/usr/bin/env python3
# Updated: 2018-10-17
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 $__savedpath $d; 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")
return "\n".join(res)
if __name__ == "__main__":
to_convert = {
"~/.nix-profile/etc/profile.d/nix.sh" : "~/.config/fish/conf.d/nix.fish",
# NOTE: We don't start nix-daemon automatically
"~/.nix-profile/etc/profile.d/nix-daemon.sh":"~/.config/fish/nix-daemon.fish",
}
installed = 0
for src,dst in to_convert.items():
src,dst = (os.path.expanduser(_) for _ in (src,dst))
if not os.path.exists(src):
continue
installed += 1
with open(os.path.expanduser(src), "rt") as f:
t = process(f.read(), src)
if not os.path.exists(os.path.dirname(dst)):
os.makedirs(os.path.dirname(dst))
with open(dst, "wt") as g:
g.write(t)
sys.stderr.write(" To use Nix from Fish: source {0}\n".format(dst))
if not installed:
sys.stderr.write("[!] Nix does not seem to be installed, expecting {0}\n".format(", ".join(to_convert.keys())))
sys.exit(-1)
# EOF - vim: ts=4 sw=4 noet
@princed
Copy link

princed commented Jul 16, 2018

@mamciek
Copy link

mamciek commented Sep 14, 2018

There is a small bug in this script. It reverses the order of PATH variable. Change line 74 to
return " for d in $PATH; if test -e $d; set __savedpath $__savedpath $d; end; end;"

@digitalknk
Copy link

@sebastien After version 2.3.0 of Fish anything located in ~/.config/fish/conf.d/ will auto load. Copying the generated nix.fish file in that directory loads it right up.

@sebastien
Copy link
Author

I updated the script with all of the above changes. Thanks!

@PascalLeMerrer
Copy link

I used this, and executing the generated script fails:

~/.config/fish/nix-daemon.fish (line 59): Missing end to balance this if statement
if test ! -z "$NIX_SSL_CERT_FILE" ;
^
from sourcing file ~/.config/fish/nix-daemon.fish

@jduan
Copy link

jduan commented May 17, 2020

I used this script to generate fish script and I got this error:

~/.config/fish/conf.d/nix.fish (line 39): ${ is not a valid variable in fish.
    if test -n "${MANPATH-}" ;

I fixed it by changing ${MANPATH-} to $MANPATH

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment