rc.xsh 10 KB

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