Skip to content

Instantly share code, notes, and snippets.

@mamciek
Forked from sebastien/nix-fishgen.py
Last active September 14, 2018 12:51
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 mamciek/805c737ff680dd8e034f27e876aa3a00 to your computer and use it in GitHub Desktop.
Save mamciek/805c737ff680dd8e034f27e876aa3a00 to your computer and use it in GitHub Desktop.
Python script that converts Nix's profile bash script to Fish
#!/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*)fi(\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 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__":
src = "~/.nix-profile/etc/profile.d/nix.sh"
dst = "~/.config/fish/nix.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