__bass.py 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. """
  2. To be used with a companion fish function like this:
  3. function refish
  4. set -l _x (python /tmp/bass.py source ~/.nvm/nvim.sh ';' nvm use iojs); source $_x; and rm -f $_x
  5. end
  6. """
  7. from __future__ import print_function
  8. import json
  9. import subprocess
  10. import sys
  11. import traceback
  12. BASH = 'bash'
  13. def comment(string):
  14. return '\n'.join(['# ' + line for line in string.split('\n')])
  15. def gen_script():
  16. divider = '-__-__-__bass___-env-output-__bass_-__-__-__-__'
  17. # Use the following instead of /usr/bin/env to read environment so we can
  18. # deal with multi-line environment variables (and other odd cases).
  19. env_reader = "python -c 'import os,json; print(json.dumps({k:v for k,v in os.environ.items()}))'"
  20. args = [BASH, '-c', env_reader]
  21. output = subprocess.check_output(args, universal_newlines=True)
  22. old_env = output.strip()
  23. command = '{} && (echo "{}"; {}; echo "{}"; alias)'.format(
  24. ' '.join(sys.argv[1:]).rstrip().rstrip(';'),
  25. divider,
  26. env_reader,
  27. divider,
  28. )
  29. args = [BASH, '-c', command]
  30. output = subprocess.check_output(args, universal_newlines=True)
  31. stdout, new_env, alias = output.split(divider, 2)
  32. new_env = new_env.strip()
  33. old_env = json.loads(old_env)
  34. new_env = json.loads(new_env)
  35. script_lines = []
  36. for line in stdout.splitlines():
  37. # some outputs might use documentation about the shell usage with dollar signs
  38. line = line.replace(r'$', r'\$')
  39. script_lines.append("printf %s;printf '\\n'" % json.dumps(line))
  40. for k, v in new_env.items():
  41. if k in ['PS1', 'SHLVL', 'XPC_SERVICE_NAME'] or k.startswith("BASH_FUNC"):
  42. continue
  43. v1 = old_env.get(k)
  44. if not v1:
  45. script_lines.append(comment('adding %s=%s' % (k, v)))
  46. elif v1 != v:
  47. script_lines.append(comment('updating %s=%s -> %s' % (k, v1, v)))
  48. # process special variables
  49. if k == 'PWD':
  50. script_lines.append('cd %s' % json.dumps(v))
  51. continue
  52. else:
  53. continue
  54. if k == 'PATH':
  55. # use json.dumps to reliably escape quotes and backslashes
  56. value = ' '.join([json.dumps(directory)
  57. for directory in v.split(':')])
  58. else:
  59. # use json.dumps to reliably escape quotes and backslashes
  60. value = json.dumps(v)
  61. script_lines.append('set -g -x %s %s' % (k, value))
  62. for var in set(old_env.keys()) - set(new_env.keys()):
  63. script_lines.append(comment('removing %s' % var))
  64. script_lines.append('set -e %s' % var)
  65. script = '\n'.join(script_lines)
  66. return script + '\n' + alias
  67. if not sys.argv[1:]:
  68. print('__usage', end='')
  69. sys.exit(0)
  70. try:
  71. script = gen_script()
  72. except subprocess.CalledProcessError as e:
  73. print('exit code:', e.returncode, file=sys.stderr)
  74. print('__error', e.returncode, end='')
  75. except Exception as e:
  76. print('unknown error:', str(e), file=sys.stderr)
  77. traceback.print_exc(10, file=sys.stderr)
  78. print('__error', end='')
  79. else:
  80. print(script, end='')