rc.xsh 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  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. $_Z_EXCLUDE_DIRS = ['/tmp']
  7. xontrib load vox z
  8. def _last_exit_status():
  9. try:
  10. exit_status = __xonsh_history__.rtns[-1]
  11. return exit_status if exit_status != 0 else None
  12. except IndexError:
  13. return None
  14. $PROMPT_FIELDS['last_exit_status'] = _last_exit_status
  15. $SHLVL = int($SHLVL) + 1 if 'SHLVL' in ${...} else 1
  16. $XONSH_STDERR_PREFIX = '{RED}'
  17. $XONSH_STDERR_POSTFIX = '{NO_COLOR}'
  18. $DYNAMIC_CWD_WIDTH = '30%'
  19. $DYNAMIC_CWD_ELISION_CHAR = '…'
  20. $PROMPT = ''.join([
  21. '{RED}{last_exit_status:[{}] }',
  22. '{BOLD_GREEN}{user}@{hostname} ',
  23. '{YELLOW}{cwd} ',
  24. '{{BLUE}}{} '.format('{prompt_end}' * $SHLVL),
  25. '{NO_COLOR}',
  26. ])
  27. $RIGHT_PROMPT = '{gitstatus}{env_name: {}}'
  28. $XONSH_APPEND_NEWLINE = True
  29. import contextlib
  30. import datetime as dt
  31. import io
  32. import os
  33. import re
  34. import shutil
  35. import stat
  36. import subprocess
  37. import sys
  38. os.umask(stat.S_IWGRP | stat.S_IRWXO) # 027
  39. # default locale
  40. # will be used for all non-explicitly set LC_* variables
  41. $LANG = 'en_US.UTF-8'
  42. # fallback locales
  43. # GNU gettext gives preference to LANGUAGE over LC_ALL and LANG
  44. # for the purpose of message handling
  45. # https://www.gnu.org/software/gettext/manual/html_node/The-LANGUAGE-variable.html
  46. # cave: if this list contains 'de(_.*)?' at any (sic!) position
  47. # vim 7.4.1689 will switch to german
  48. $LANGUAGE = ':'.join(['en_US', 'en'])
  49. $LC_COLLATE = 'C.UTF-8'
  50. # char classification, case conversion & other char attrs
  51. $LC_CTYPE = 'de_AT.UTF-8'
  52. # $ locale currency_symbol
  53. $LC_MONETARY = 'de_AT.UTF-8'
  54. # $ locale -k LC_NUMERIC | head -n 3
  55. # decimal_point="."
  56. # thousands_sep=""
  57. # grouping=-1
  58. $LC_NUMERIC = 'C.UTF-8'
  59. # A4
  60. $LC_PAPER = 'de_AT.UTF-8'
  61. USER_BIN_PATH = os.path.join($HOME, '.local', 'bin')
  62. if os.path.isdir(USER_BIN_PATH):
  63. $PATH.insert(0, USER_BIN_PATH)
  64. $PAGER = 'less'
  65. $EDITOR = 'vim'
  66. # i3-sensible-terminal
  67. $TERMINAL = 'termite'
  68. if shutil.which('gpgconf'):
  69. # required by pinentry-tty when using gpg command
  70. # takes approx 100ms in termux on galaxy S7
  71. $GPG_TTY = $(tty)
  72. # required by scute
  73. $GPG_AGENT_INFO = $(gpgconf --list-dir agent-socket).rstrip() + ':0:1'
  74. if not 'SSH_CLIENT' in ${...}:
  75. # in gnupg 2.1.13 the location of agents socket changed
  76. $SSH_AUTH_SOCK = $(gpgconf --list-dir agent-ssh-socket).rstrip()
  77. # wrapper for termite required when launching termite from ranger:
  78. $TERMCMD = os.path.join(os.path.dirname(__file__), 'ranger-termite-termcmd')
  79. # https://docs.docker.com/engine/security/trust/content_trust/
  80. $DOCKER_CONTENT_TRUST = 1
  81. class DockerImage:
  82. def __init__(self, image):
  83. import json
  84. attrs, = json.loads(subprocess.check_output(['sudo', 'docker', 'image', 'inspect', image])
  85. .decode(sys.stdout.encoding))
  86. self._id = attrs['Id']
  87. self._tags = attrs['RepoTags']
  88. def __repr__(self):
  89. return '{}(id={!r}, tags={!r})'.format(type(self).__name__, self._id, self._tags)
  90. @classmethod
  91. def build(cls, dockerfile_or_path, tag=None):
  92. out = io.BytesIO()
  93. args = ['sudo', 'docker', 'build']
  94. if tag:
  95. args.extend(['--tag', tag])
  96. with StdoutTee(out) as tee:
  97. if os.path.exists(dockerfile_or_path):
  98. p = subprocess.Popen(args + [dockerfile_or_path],
  99. stdin=None, stdout=tee)
  100. else:
  101. p = subprocess.Popen(args + ['-'],
  102. stdin=subprocess.PIPE, stdout=tee)
  103. p.stdin.write(dockerfile_or_path.encode())
  104. p.stdin.close()
  105. assert p.wait() == 0, 'docker build failed'
  106. image_id, = re.search(rb'^Successfully built (\S+)$', out.getvalue(), re.MULTILINE).groups()
  107. return cls(image_id.decode(sys.stdout.encoding))
  108. @classmethod
  109. def pull(cls, image):
  110. out = io.BytesIO()
  111. with StdoutTee(out) as tee:
  112. subprocess.run(['sudo', 'docker', 'image', 'pull', image], stdout=tee)
  113. repo_digest, = re.search(rb'^Digest: (sha\S+:\S+)$', out.getvalue(), re.MULTILINE).groups()
  114. return cls('{}@{}'.format(image, repo_digest.decode()))
  115. def run(self, args=[], name=None, detach=False, env={},
  116. network=None, publish_ports=[], volumes=[], caps=[]):
  117. params = ['sudo', 'docker', 'run', '--rm']
  118. if name:
  119. params.extend(['--name', name])
  120. if detach:
  121. params.append('--detach')
  122. else:
  123. params.extend(['--interactive', '--tty'])
  124. params.extend([a for k, v in env.items() for a in ['--env', '{}={}'.format(k, v)]])
  125. if network:
  126. params.extend(['--network', network])
  127. params.extend([a for v in volumes for a in ['--volume', ':'.join(v)]])
  128. params.extend('--publish=' + ':'.join([str(a) for a in p]) for p in publish_ports)
  129. params.extend(['--security-opt=no-new-privileges', '--cap-drop=all'])
  130. params.extend(['--cap-add={}'.format(c) for c in caps])
  131. params.append(self._id)
  132. params.extend(args)
  133. sys.stderr.write('{}\n'.format(shlex_join(params)))
  134. subprocess.run(params, check=True)
  135. class StdoutTee:
  136. def __init__(self, sink):
  137. self._sink = sink
  138. def __enter__(self):
  139. self._read_fd, self._write_fd = os.pipe()
  140. import threading
  141. self._thread = threading.Thread(target=self._loop)
  142. self._thread.start()
  143. return self
  144. def _loop(self):
  145. while True:
  146. try:
  147. data = os_read_non_blocking(self._read_fd)
  148. except OSError: # fd closed
  149. return
  150. if data:
  151. self._sink.write(data)
  152. sys.stdout.buffer.write(data)
  153. sys.stdout.flush()
  154. def fileno(self):
  155. return self._write_fd
  156. def __exit__(self, exc_type, exc_value, traceback):
  157. os.close(self._read_fd)
  158. os.close(self._write_fd)
  159. self._thread.join()
  160. @contextlib.contextmanager
  161. def chdir(path):
  162. previous = os.getcwd()
  163. try:
  164. os.chdir(path)
  165. yield path
  166. finally:
  167. os.chdir(previous)
  168. def dpkg_listfiles(pkg_name):
  169. assert isinstance(pkg_name, str)
  170. paths = $(dpkg --listfiles @(pkg_name)).split('\n')[:-1]
  171. assert len(paths) > 0, 'pkg {!r} not installed'.format(pkg_name)
  172. return paths
  173. def dpkg_search(path_search_pattern):
  174. assert isinstance(path_search_pattern, str)
  175. return re.findall(
  176. '^(\S+): (.*)$\n',
  177. $(dpkg --search @(path_search_pattern)),
  178. flags=re.MULTILINE,
  179. )
  180. def dpkg_welse(cmd):
  181. pkg_name, cmd_path = dpkg_which(cmd)
  182. return dpkg_listfiles(pkg_name)
  183. def dpkg_which(cmd):
  184. cmd_path = shutil.which(cmd)
  185. assert cmd_path, 'cmd {!r} not found'.format(cmd)
  186. matches = dpkg_search(cmd_path)
  187. assert len(matches) != 0, '{!r} not installed via dpkg'.format(cmd_path)
  188. assert len(matches) == 1
  189. return matches[0]
  190. @contextlib.contextmanager
  191. def encfs_mount(root_dir_path, mount_point_path, extpass=None):
  192. mount_arg_patterns = ['encfs', root_dir_path, mount_point_path]
  193. if extpass:
  194. mount_arg_patterns.extend(['--extpass', shlex_join(extpass)])
  195. with fuse_mount(mount_arg_patterns=mount_arg_patterns,
  196. mount_point_path=mount_point_path):
  197. yield mount_point_path
  198. @contextlib.contextmanager
  199. def fuse_mount(mount_arg_patterns, mount_point_path):
  200. import shlex
  201. mount_args = [a.format(mp=shlex.quote(mount_point_path))
  202. for a in mount_arg_patterns]
  203. sys.stderr.write('{}\n'.format(shlex_join(mount_args)))
  204. subprocess.check_call(mount_args)
  205. try:
  206. yield mount_point_path
  207. finally:
  208. umount_args = ['fusermount', '-u', '-z', mount_point_path]
  209. sys.stderr.write('{}\n'.format(shlex_join(umount_args)))
  210. subprocess.check_call(umount_args)
  211. def gpg_decrypt(path, verify=False):
  212. import gpg
  213. with gpg.Context() as gpg_ctx:
  214. with open(path, 'rb') as f:
  215. data, decrypt_result, verify_result = gpg_ctx.decrypt(f, verify=verify)
  216. return data
  217. def locate(*patterns, match_all=True, ignore_case=True):
  218. params = []
  219. if match_all:
  220. params.insert(0, '--all')
  221. if ignore_case:
  222. params.insert(0, '--ignore-case')
  223. return $(locate @(params) -- @(patterns)).split('\n')[:-1]
  224. def os_read_non_blocking(fd, buffer_size_bytes=8*1024, timeout_seconds=0.1):
  225. import select
  226. if fd in select.select([fd], [], [], timeout_seconds)[0]:
  227. return os.read(fd, buffer_size_bytes)
  228. else:
  229. return None
  230. def shlex_join(params):
  231. import shlex
  232. assert isinstance(params, list) or isinstance(params, tuple), params
  233. return ' '.join(shlex.quote(p) for p in params)
  234. def timestamp_now_utc():
  235. return dt.datetime.utcnow().replace(tzinfo=dt.timezone.utc)
  236. def timestamp_now_local():
  237. # if called without tz argument astimezone() assumes
  238. # the system local timezone for the target timezone
  239. return timestamp_now_utc().astimezone()
  240. def timestamp_iso_local():
  241. # if called without tz argument astimezone() assumes
  242. # the system local timezone for the target timezone
  243. return timestamp_now_local().strftime('%Y%m%dT%H%M%S%z')
  244. def yaml_load(path):
  245. import yaml
  246. with open(path, 'r') as f:
  247. return yaml.load(f.read())
  248. def yaml_write(path, data):
  249. import yaml
  250. with open(path, 'w') as f:
  251. f.write(yaml.dump(data, default_flow_style=False))
  252. aliases['d'] = ['sudo', 'docker']
  253. aliases['dpkg-welse'] = lambda args: '\n'.join(dpkg_welse(args[0]))
  254. aliases['dpkg-which'] = lambda args: '\t'.join(dpkg_which(args[0]))
  255. aliases['g'] = ['git']
  256. aliases['ll'] = ['ls', '-l', '--all', '--indicator-style=slash',
  257. '--human-readable', '--time-style=long-iso', '--color=auto']
  258. if shutil.which('startx') and $(tty).rstrip() == '/dev/tty1':
  259. startx
  260. # vim: filetype=python