rc.xsh 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. $VI_MODE = True
  2. $AUTO_PUSHD = True
  3. $XONSH_AUTOPAIR = True
  4. # tab selection: do not execute cmd when pressing enter
  5. $COMPLETIONS_CONFIRM = True
  6. xontrib load vox z
  7. # mock ZHandler.add to keep it from adding dirs in /tmp
  8. ZHandler = aliases['z'].__self__ if 'z' in aliases else None
  9. if ZHandler:
  10. ZHandler_add = ZHandler.add
  11. def zhandler_add_mock(self, path):
  12. if not path.startswith('/tmp'):
  13. ZHandler_add(self, path)
  14. else:
  15. print('z ignore', path)
  16. ZHandler.add = zhandler_add_mock
  17. def _last_exit_status():
  18. try:
  19. exit_status = __xonsh_history__.rtns[-1]
  20. return exit_status if exit_status != 0 else None
  21. except IndexError:
  22. return None
  23. $PROMPT_FIELDS['last_exit_status'] = _last_exit_status
  24. $SHLVL = int($SHLVL) + 1 if 'SHLVL' in ${...} else 1
  25. $XONSH_STDERR_PREFIX = '{RED}'
  26. $XONSH_STDERR_POSTFIX = '{NO_COLOR}'
  27. $DYNAMIC_CWD_WIDTH = '30%'
  28. $DYNAMIC_CWD_ELISION_CHAR = '…'
  29. $PROMPT = ''.join([
  30. '{RED}{last_exit_status:[{}] }',
  31. '{BOLD_GREEN}{user}@{hostname} ',
  32. '{YELLOW}{cwd} ',
  33. '{{BLUE}}{} '.format('{prompt_end}' * $SHLVL),
  34. '{NO_COLOR}',
  35. ])
  36. $RIGHT_PROMPT = '{gitstatus}{env_name: {}}'
  37. import datetime as dt
  38. import io
  39. import json
  40. import os
  41. import re
  42. import select
  43. import shlex
  44. import shutil
  45. import subprocess
  46. import sys
  47. import threading
  48. # default locale
  49. # will be used for all non-explicitly set LC_* variables
  50. $LANG = 'en_US.UTF-8'
  51. # fallback locales
  52. # GNU gettext gives preference to LANGUAGE over LC_ALL and LANG
  53. # for the purpose of message handling
  54. # https://www.gnu.org/software/gettext/manual/html_node/The-LANGUAGE-variable.html
  55. # cave: if this list contains 'de(_.*)?' at any (sic!) position
  56. # vim 7.4.1689 will switch to german
  57. $LANGUAGE = ':'.join(['en_US', 'en'])
  58. $LC_COLLATE = 'C.UTF-8'
  59. # char classification, case conversion & other char attrs
  60. $LC_CTYPE = 'de_AT.UTF-8'
  61. # $ locale currency_symbol
  62. $LC_MONETARY = 'de_AT.UTF-8'
  63. # $ locale -k LC_NUMERIC | head -n 3
  64. # decimal_point="."
  65. # thousands_sep=""
  66. # grouping=-1
  67. $LC_NUMERIC = 'C.UTF-8'
  68. # A4
  69. $LC_PAPER = 'de_AT.UTF-8'
  70. USER_BIN_PATH = os.path.join($HOME, '.local', 'bin')
  71. if os.path.isdir(USER_BIN_PATH):
  72. $PATH.insert(0, USER_BIN_PATH)
  73. $PAGER = 'less'
  74. $EDITOR = 'vim'
  75. # required by pinentry-tty when using gpg command:
  76. $GPG_TTY = $(tty)
  77. if shutil.which('gpgconf'):
  78. # required by scute
  79. $GPG_AGENT_INFO = $(gpgconf --list-dir agent-socket).rstrip() + ':0:1'
  80. if not 'SSH_CLIENT' in ${...}:
  81. # in gnupg 2.1.13 the location of agents socket changed
  82. $SSH_AUTH_SOCK = $(gpgconf --list-dir agent-ssh-socket).rstrip()
  83. # wrapper for termite required when launching termite from ranger:
  84. $TERMCMD = os.path.join(os.path.dirname(__file__), 'ranger-termite-termcmd')
  85. # https://docs.docker.com/engine/security/trust/content_trust/
  86. $DOCKER_CONTENT_TRUST = 1
  87. class DockerImage:
  88. def __init__(self, image):
  89. attrs, = json.loads(subprocess.check_output(['sudo', 'docker', 'image', 'inspect', image])
  90. .decode(sys.stdout.encoding))
  91. self._id = attrs['Id']
  92. self._tags = attrs['RepoTags']
  93. def __repr__(self):
  94. return '{}(id={!r}, tags={!r})'.format(type(self).__name__, self._id, self._tags)
  95. @classmethod
  96. def build(cls, dockerfile):
  97. out = io.BytesIO()
  98. with StdoutTee(out) as tee:
  99. p = subprocess.Popen(['sudo', 'docker', 'build', '-'],
  100. stdin=subprocess.PIPE, stdout=tee)
  101. p.stdin.write(dockerfile.encode())
  102. p.stdin.close()
  103. assert p.wait() == 0, 'docker build failed'
  104. image_id, = re.search(rb'^Successfully built (\S+)$', out.getvalue(), re.MULTILINE).groups()
  105. return cls(image_id.decode(sys.stdout.encoding))
  106. @classmethod
  107. def pull(cls, image):
  108. out = io.BytesIO()
  109. with StdoutTee(out) as tee:
  110. subprocess.run(['sudo', 'docker', 'image', 'pull', image], stdout=tee)
  111. repo_digest, = re.search(rb'^Digest: (sha\S+:\S+)$', out.getvalue(), re.MULTILINE).groups()
  112. return cls('{}@{}'.format(image, repo_digest.decode()))
  113. def run(self, detach=False, publish_ports=[], args=[], caps=[]):
  114. params = ['sudo', 'docker', 'run', '--rm']
  115. if detach:
  116. params.append('--detach')
  117. else:
  118. params.extend(['--interactive', '--tty'])
  119. params.extend('--publish=' + ':'.join([str(a) for a in p]) for p in publish_ports)
  120. params.extend(['--security-opt=no-new-privileges', '--cap-drop=all'])
  121. params.extend(['--cap-add={}'.format(c) for c in caps])
  122. params.append(self._id)
  123. params.extend(args)
  124. sys.stderr.write('{}\n'.format(shlex_join(params)))
  125. subprocess.run(params, check=True)
  126. class StdoutTee:
  127. def __init__(self, sink):
  128. self._sink = sink
  129. def __enter__(self):
  130. self._read_fd, self._write_fd = os.pipe()
  131. self._thread = threading.Thread(target=self._loop)
  132. self._thread.start()
  133. return self
  134. def _loop(self):
  135. while True:
  136. try:
  137. data = os_read_non_blocking(self._read_fd)
  138. except OSError: # fd closed
  139. return
  140. if data:
  141. self._sink.write(data)
  142. sys.stdout.buffer.write(data)
  143. sys.stdout.flush()
  144. def fileno(self):
  145. return self._write_fd
  146. def __exit__(self, exc_type, exc_value, traceback):
  147. os.close(self._read_fd)
  148. os.close(self._write_fd)
  149. self._thread.join()
  150. def dpkg_listfiles(pkg_name):
  151. assert isinstance(pkg_name, str)
  152. paths = $(dpkg --listfiles @(pkg_name)).split('\n')[:-1]
  153. assert len(paths) > 0, 'pkg {!r} not installed'.format(pkg_name)
  154. return paths
  155. def dpkg_search(path_search_pattern):
  156. assert isinstance(path_search_pattern, str)
  157. return re.findall(
  158. '^(\S+): (.*)$\n',
  159. $(dpkg --search @(path_search_pattern)),
  160. flags=re.MULTILINE,
  161. )
  162. def dpkg_welse(cmd):
  163. pkg_name, cmd_path = dpkg_which(cmd)
  164. return dpkg_listfiles(pkg_name)
  165. def dpkg_which(cmd):
  166. cmd_path = shutil.which(cmd)
  167. assert cmd_path, 'cmd {!r} not found'.format(cmd)
  168. matches = dpkg_search(cmd_path)
  169. assert len(matches) != 0, '{!r} not installed via dpkg'.format(cmd_path)
  170. assert len(matches) == 1
  171. return matches[0]
  172. def locate(*patterns, match_all=True, ignore_case=True):
  173. params = []
  174. if match_all:
  175. params.insert(0, '--all')
  176. if ignore_case:
  177. params.insert(0, '--ignore-case')
  178. return $(locate @(params) -- @(patterns)).split('\n')[:-1]
  179. def os_read_non_blocking(fd, buffer_size_bytes=8*1024, timeout_seconds=0.1):
  180. if fd in select.select([fd], [], [], timeout_seconds)[0]:
  181. return os.read(fd, buffer_size_bytes)
  182. else:
  183. return None
  184. def shlex_join(params):
  185. return ' '.join(shlex.quote(p) for p in params)
  186. def timestamp_now_utc():
  187. return dt.datetime.utcnow().replace(tzinfo=dt.timezone.utc)
  188. def timestamp_now_local():
  189. # if called without tz argument astimezone() assumes
  190. # the system local timezone for the target timezone
  191. return timestamp_now_utc().astimezone()
  192. def timestamp_iso_local():
  193. # if called without tz argument astimezone() assumes
  194. # the system local timezone for the target timezone
  195. return timestamp_now_local().strftime('%Y%m%dT%H%M%S%z')
  196. aliases['d'] = ['sudo', 'docker']
  197. aliases['dpkg-welse'] = lambda args: '\n'.join(dpkg_welse(args[0]))
  198. aliases['dpkg-which'] = lambda args: '\t'.join(dpkg_which(args[0]))
  199. aliases['g'] = ['git']
  200. aliases['ll'] = ['ls', '-l', '--all', '--indicator-style=slash',
  201. '--human-readable', '--time-style=long-iso', '--color=auto']
  202. if shutil.which('startx') and $(tty).rstrip() == '/dev/tty1':
  203. startx
  204. # vim: filetype=python