File: shell.py

package info (click to toggle)
crazy-complete 0.3.6-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 2,404 kB
  • sloc: python: 7,949; sh: 4,636; makefile: 74
file content (167 lines) | stat: -rw-r--r-- 6,056 bytes parent folder | download | duplicates (2)
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
'''Shell utility functions.'''

import re
import collections

from . import cli
from . import utils


def make_identifier(string):
    '''Make `string` a valid shell identifier.

    This function replaces any dashes '-' with underscores '_',
    removes any characters that are not letters, digits, or underscores,
    and ensures that consecutive underscores are replaced with a single underscore.

    Args:
        string (str): The input string to be converted into a valid shell identifier.

    Returns:
        str: The modified string that is a valid shell identifier.
    '''

    string = string.replace('-', '_')
    string = re.sub('[^a-zA-Z0-9_]', '', string)
    string = re.sub('_+', '_', string)
    if string and string[0] in '0123456789':
        return '_' + string
    return string


def needs_escape(string):
    '''Return if string needs escaping.'''

    return not re.fullmatch('[a-zA-Z0-9_@%+=:,./-]+', string)


def escape(string, escape_empty_string=True):
    '''Escapes special characters in a string for safe usage in shell commands or scripts.

    Args:
        string (str): The input string to be escaped.
        escape_empty_string (bool, optional): Determines whether to escape an empty string or not.
            Defaults to True.

    Returns:
        str: The escaped string.
    '''

    if not string and not escape_empty_string:
        return ''

    if not needs_escape(string):
        return string

    if "'" not in string:
        return "'%s'" % string

    if '"' not in string:
        return '"%s"' % string.replace('\\', '\\\\').replace('$', '\\$').replace('`', '\\`')

    return "'%s'" % string.replace("'", '\'"\'"\'')


def make_completion_funcname(cmdline, prefix='_', suffix=''):
    '''Generates a function name for auto-completing a program or subcommand.

    Args:
        cmdline (CommandLine): The CommandLine instance representing the program or subcommand.
        prefix (str): The prefix that shall be prepended to the result.
        suffix (str): The suffix that shall be appended to the result.

    Returns:
        str: The generated function name for auto-completion.

    This function is used to generate a unique function name for auto-completing
    a program or subcommand in the specified shell. It concatenates the names of
    all parent commandlines, including the current commandline, and converts them
    into a valid function name format.

    Example:
        For a program with the name 'my_program' and a subcommand with the name 'subcommand',
        the generated function name is '_my_program_subcommand'.
    '''
    assert isinstance(cmdline, cli.CommandLine), \
        "make_completion_funcname: cmdline: expected CommandLine, got %r" % cmdline

    commandlines = cmdline.get_parents(include_self=True)
    identifier = make_identifier('_'.join(p.prog for p in commandlines))

    return f'{prefix}{identifier}{suffix}'


class ShellCompleter:
    '''Base class for argument completion.'''

    # pylint: disable=missing-function-docstring

    def complete(self, ctxt, completion, *a):
        if not hasattr(self, completion):
            utils.warn(f"ShellCompleter: Falling back from `{completion}` to `none`")
            completion = 'none'

        return getattr(self, completion)(ctxt, *a)

    def fallback(self, ctxt, from_, to, *a):
        utils.warn(f"ShellCompleter: Falling back from `{from_}` to `{to}`")
        return self.complete(ctxt, to, *a)

    def signal(self, ctxt, prefix=''):
        sig = prefix
        signals = collections.OrderedDict([
            (sig+'ABRT',   'Process abort signal'),
            (sig+'ALRM',   'Alarm clock'),
            (sig+'BUS',    'Access to an undefined portion of a memory object'),
            (sig+'CHLD',   'Child process terminated, stopped, or continued'),
            (sig+'CONT',   'Continue executing, if stopped'),
            (sig+'FPE',    'Erroneous arithmetic operation'),
            (sig+'HUP',    'Hangup'),
            (sig+'ILL',    'Illegal instruction'),
            (sig+'INT',    'Terminal interrupt signal'),
            (sig+'KILL',   'Kill (cannot be caught or ignored)'),
            (sig+'PIPE',   'Write on a pipe with no one to read it'),
            (sig+'QUIT',   'Terminal quit signal'),
            (sig+'SEGV',   'Invalid memory reference'),
            (sig+'STOP',   'Stop executing (cannot be caught or ignored)'),
            (sig+'TERM',   'Termination signal'),
            (sig+'TSTP',   'Terminal stop signal'),
            (sig+'TTIN',   'Background process attempting read'),
            (sig+'TTOU',   'Background process attempting write'),
            (sig+'USR1',   'User-defined signal 1'),
            (sig+'USR2',   'User-defined signal 2'),
            (sig+'POLL',   'Pollable event'),
            (sig+'PROF',   'Profiling timer expired'),
            (sig+'SYS',    'Bad system call'),
            (sig+'TRAP',   'Trace/breakpoint trap'),
            (sig+'XFSZ',   'File size limit exceeded'),
            (sig+'VTALRM', 'Virtual timer expired'),
            (sig+'XCPU',   'CPU time limit exceeded'),
        ])

        return self.complete(ctxt, 'choices', signals)

    def range(self, ctxt, start, stop, step=1):
        return self.fallback(ctxt, 'range', 'choices', list(range(start, stop, step)))

    def directory(self, ctxt, opts):
        return self.fallback(ctxt, 'directory', 'file', opts)

    def command(self, ctxt):
        return self.fallback(ctxt, 'command', 'file')

    # =========================================================================
    # Bonus
    # =========================================================================

    def login_shell(self, ctxt):
        return self.exec(ctxt, "command grep -E '^[^#]' /etc/shells")

    def locale(self, ctxt):
        return self.exec(ctxt, "command locale -a")

    def charset(self, ctxt):
        return self.exec(ctxt, "command locale -m")

    def mountpoint(self, ctxt):
        return self.exec(ctxt, "command mount | command cut -d' ' -f3")