aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/argcomplete/py3/argcomplete/scripts/activate_global_python_argcomplete.py
blob: 299d081c0ea81068bf4f4ebacbef65a856195f46 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#!/usr/bin/env python3
# PYTHON_ARGCOMPLETE_OK

# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors.
# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.

"""
Activate the generic bash-completion script or zsh completion autoload function for the argcomplete module.
"""

import argparse
import os
import shutil
import site
import subprocess
import sys

import argcomplete

# PEP 366
__package__ = "argcomplete.scripts"

zsh_shellcode = """
# Begin added by argcomplete
fpath=( {zsh_fpath} "${{fpath[@]}}" )
# End added by argcomplete
"""

bash_shellcode = """
# Begin added by argcomplete
source "{activator}"
# End added by argcomplete
"""

parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("-y", "--yes", help="automatically answer yes for all questions", action="store_true")
parser.add_argument("--dest", help='Specify the shell completion modules directory to install into, or "-" for stdout')
parser.add_argument("--user", help="Install into user directory", action="store_true")
argcomplete.autocomplete(parser)
args = None


def get_local_dir():
    try:
        return subprocess.check_output(["brew", "--prefix"]).decode().strip()
    except (FileNotFoundError, subprocess.CalledProcessError):
        return "/usr/local"


def get_zsh_system_dir():
    return f"{get_local_dir()}/share/zsh/site-functions"


def get_bash_system_dir():
    if "BASH_COMPLETION_COMPAT_DIR" in os.environ:
        return os.environ["BASH_COMPLETION_COMPAT_DIR"]
    elif sys.platform == "darwin":
        return f"{get_local_dir()}/etc/bash_completion.d"  # created by homebrew
    else:
        return "/etc/bash_completion.d"  # created by bash-completion


def get_activator_dir():
    return os.path.join(os.path.abspath(os.path.dirname(argcomplete.__file__)), "bash_completion.d")


def get_activator_path():
    return os.path.join(get_activator_dir(), "_python-argcomplete")


def install_to_destination(dest):
    activator = get_activator_path()
    if dest == "-":
        with open(activator) as fh:
            sys.stdout.write(fh.read())
        return
    destdir = os.path.dirname(dest)
    if not os.path.exists(destdir):
        try:
            os.makedirs(destdir, exist_ok=True)
        except Exception as e:
            parser.error(f"path {destdir} does not exist and could not be created: {e}")
    try:
        print(f"Installing {activator} to {dest}...", file=sys.stderr)
        shutil.copy(activator, dest)
        print("Installed.", file=sys.stderr)
    except Exception as e:
        parser.error(
            f"while installing to {dest}: {e}. Please run this command using sudo, or see --help for more options."
        )


def get_consent():
    assert args is not None
    if args.yes is True:
        return True
    while True:
        res = input("OK to proceed? [y/n] ")
        if res.lower() not in {"y", "n", "yes", "no"}:
            print('Please answer "yes" or "no".', file=sys.stderr)
        elif res.lower() in {"y", "yes"}:
            return True
        else:
            return False


def append_to_config_file(path, shellcode):
    if os.path.exists(path):
        with open(path, 'r') as fh:
            if shellcode in fh.read():
                print(f"The code already exists in the file {path}.", file=sys.stderr)
                return
        print(f"argcomplete needs to append to the file {path}. The following code will be appended:", file=sys.stderr)
        for line in shellcode.splitlines():
            print(">", line, file=sys.stderr)
        if not get_consent():
            print("Not added.", file=sys.stderr)
            return
    print(f"Adding shellcode to {path}...", file=sys.stderr)
    with open(path, "a") as fh:
        fh.write(shellcode)
    print("Added.", file=sys.stderr)

def link_zsh_user_rcfile(zsh_fpath=None):
    zsh_rcfile = os.path.join(os.path.expanduser(os.environ.get("ZDOTDIR", "~")), ".zshenv")
    append_to_config_file(zsh_rcfile, zsh_shellcode.format(zsh_fpath=zsh_fpath or get_activator_dir()))

def link_bash_user_rcfile():
    bash_completion_user_file = os.path.expanduser("~/.bash_completion")
    append_to_config_file(bash_completion_user_file, bash_shellcode.format(activator=get_activator_path()))


def link_user_rcfiles():
    # TODO: warn if running as superuser
    link_zsh_user_rcfile()
    link_bash_user_rcfile()

def add_zsh_system_dir_to_fpath_for_user():
    if "zsh" not in os.environ.get("SHELL", ""):
        return
    try:
        zsh_system_dir = get_zsh_system_dir()
        fpath_output = subprocess.check_output([os.environ["SHELL"], "-c", 'printf "%s\n" "${fpath[@]}"'])
        for fpath in fpath_output.decode().splitlines():
            if fpath == zsh_system_dir:
                return
        link_zsh_user_rcfile(zsh_fpath=zsh_system_dir)
    except (FileNotFoundError, subprocess.CalledProcessError):
        pass

def main():
    global args
    args = parser.parse_args()

    destinations = []

    if args.dest:
        if args.dest != "-" and not os.path.exists(args.dest):
            parser.error(f"directory {args.dest} was specified via --dest, but it does not exist")
        destinations.append(args.dest)
    elif site.ENABLE_USER_SITE and site.USER_SITE and site.USER_SITE in argcomplete.__file__:
        print(
            "Argcomplete was installed in the user site local directory. Defaulting to user installation.",
            file=sys.stderr,
        )
        link_user_rcfiles()
    elif sys.prefix != sys.base_prefix:
        print("Argcomplete was installed in a virtual environment. Defaulting to user installation.", file=sys.stderr)
        link_user_rcfiles()
    elif args.user:
        link_user_rcfiles()
    else:
        print("Defaulting to system-wide installation.", file=sys.stderr)
        destinations.append(f"{get_zsh_system_dir()}/_python-argcomplete")
        destinations.append(f"{get_bash_system_dir()}/python-argcomplete")

    for destination in destinations:
        install_to_destination(destination)

    add_zsh_system_dir_to_fpath_for_user()

    if args.dest is None:
        print("Please restart your shell or source the installed file to activate it.", file=sys.stderr)


if __name__ == "__main__":
    sys.exit(main())