Skip to content

Instantly share code, notes, and snippets.

@princed
Forked from sebastien/nix-fishgen.py
Created July 16, 2018 01:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save princed/8ed52eeb8d665b00f6f8330132863157 to your computer and use it in GitHub Desktop.
Save princed/8ed52eeb8d665b00f6f8330132863157 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*|\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
@princed
Copy link
Author

princed commented Jul 16, 2018

Snippet to load from fish config:

if test -d /nix
   source ~/.config/fish/nix-daemon.fish
end

@hubertgrzeskowiak
Copy link

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.

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