activate_global_python_argcomplete.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. #!/usr/bin/env python3
  2. # PYTHON_ARGCOMPLETE_OK
  3. # Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors.
  4. # Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
  5. """
  6. Activate the generic bash-completion script or zsh completion autoload function for the argcomplete module.
  7. """
  8. import argparse
  9. import os
  10. import shutil
  11. import site
  12. import subprocess
  13. import sys
  14. import argcomplete
  15. # PEP 366
  16. __package__ = "argcomplete.scripts"
  17. zsh_shellcode = """
  18. # Begin added by argcomplete
  19. fpath=( {zsh_fpath} "${{fpath[@]}}" )
  20. # End added by argcomplete
  21. """
  22. bash_shellcode = """
  23. # Begin added by argcomplete
  24. source "{activator}"
  25. # End added by argcomplete
  26. """
  27. parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
  28. parser.add_argument("-y", "--yes", help="automatically answer yes for all questions", action="store_true")
  29. parser.add_argument("--dest", help='Specify the shell completion modules directory to install into, or "-" for stdout')
  30. parser.add_argument("--user", help="Install into user directory", action="store_true")
  31. argcomplete.autocomplete(parser)
  32. args = None
  33. def get_local_dir():
  34. try:
  35. return subprocess.check_output(["brew", "--prefix"]).decode().strip()
  36. except (FileNotFoundError, subprocess.CalledProcessError):
  37. return "/usr/local"
  38. def get_zsh_system_dir():
  39. return f"{get_local_dir()}/share/zsh/site-functions"
  40. def get_bash_system_dir():
  41. if "BASH_COMPLETION_COMPAT_DIR" in os.environ:
  42. return os.environ["BASH_COMPLETION_COMPAT_DIR"]
  43. elif sys.platform == "darwin":
  44. return f"{get_local_dir()}/etc/bash_completion.d" # created by homebrew
  45. else:
  46. return "/etc/bash_completion.d" # created by bash-completion
  47. def get_activator_dir():
  48. return os.path.join(os.path.abspath(os.path.dirname(argcomplete.__file__)), "bash_completion.d")
  49. def get_activator_path():
  50. return os.path.join(get_activator_dir(), "_python-argcomplete")
  51. def install_to_destination(dest):
  52. activator = get_activator_path()
  53. if dest == "-":
  54. with open(activator) as fh:
  55. sys.stdout.write(fh.read())
  56. return
  57. destdir = os.path.dirname(dest)
  58. if not os.path.exists(destdir):
  59. try:
  60. os.makedirs(destdir, exist_ok=True)
  61. except Exception as e:
  62. parser.error(f"path {destdir} does not exist and could not be created: {e}")
  63. try:
  64. print(f"Installing {activator} to {dest}...", file=sys.stderr)
  65. shutil.copy(activator, dest)
  66. print("Installed.", file=sys.stderr)
  67. except Exception as e:
  68. parser.error(
  69. f"while installing to {dest}: {e}. Please run this command using sudo, or see --help for more options."
  70. )
  71. def get_consent():
  72. assert args is not None
  73. if args.yes is True:
  74. return True
  75. while True:
  76. res = input("OK to proceed? [y/n] ")
  77. if res.lower() not in {"y", "n", "yes", "no"}:
  78. print('Please answer "yes" or "no".', file=sys.stderr)
  79. elif res.lower() in {"y", "yes"}:
  80. return True
  81. else:
  82. return False
  83. def append_to_config_file(path, shellcode):
  84. if os.path.exists(path):
  85. with open(path, 'r') as fh:
  86. if shellcode in fh.read():
  87. print(f"The code already exists in the file {path}.", file=sys.stderr)
  88. return
  89. print(f"argcomplete needs to append to the file {path}. The following code will be appended:", file=sys.stderr)
  90. for line in shellcode.splitlines():
  91. print(">", line, file=sys.stderr)
  92. if not get_consent():
  93. print("Not added.", file=sys.stderr)
  94. return
  95. print(f"Adding shellcode to {path}...", file=sys.stderr)
  96. with open(path, "a") as fh:
  97. fh.write(shellcode)
  98. print("Added.", file=sys.stderr)
  99. def link_user_rcfiles():
  100. # TODO: warn if running as superuser
  101. zsh_rcfile = os.path.join(os.path.expanduser(os.environ.get("ZDOTDIR", "~")), ".zshenv")
  102. append_to_config_file(zsh_rcfile, zsh_shellcode.format(zsh_fpath=get_activator_dir()))
  103. bash_completion_user_file = os.path.expanduser("~/.bash_completion")
  104. append_to_config_file(bash_completion_user_file, bash_shellcode.format(activator=get_activator_path()))
  105. def main():
  106. global args
  107. args = parser.parse_args()
  108. destinations = []
  109. if args.dest:
  110. if args.dest != "-" and not os.path.exists(args.dest):
  111. parser.error(f"directory {args.dest} was specified via --dest, but it does not exist")
  112. destinations.append(args.dest)
  113. elif site.ENABLE_USER_SITE and site.USER_SITE and site.USER_SITE in argcomplete.__file__:
  114. print(
  115. "Argcomplete was installed in the user site local directory. Defaulting to user installation.",
  116. file=sys.stderr,
  117. )
  118. link_user_rcfiles()
  119. elif sys.prefix != sys.base_prefix:
  120. print("Argcomplete was installed in a virtual environment. Defaulting to user installation.", file=sys.stderr)
  121. link_user_rcfiles()
  122. elif args.user:
  123. link_user_rcfiles()
  124. else:
  125. print("Defaulting to system-wide installation.", file=sys.stderr)
  126. destinations.append(f"{get_zsh_system_dir()}/_python-argcomplete")
  127. destinations.append(f"{get_bash_system_dir()}/python-argcomplete")
  128. for destination in destinations:
  129. install_to_destination(destination)
  130. if args.dest is None:
  131. print("Please restart your shell or source the installed file to activate it.", file=sys.stderr)
  132. if __name__ == "__main__":
  133. sys.exit(main())