Skip to content

Instantly share code, notes, and snippets.

@gndu91
Created January 5, 2020 20:04
Show Gist options
  • Save gndu91/d09f7bb42956eea08a0f4a6b3d6d5b93 to your computer and use it in GitHub Desktop.
Save gndu91/d09f7bb42956eea08a0f4a6b3d6d5b93 to your computer and use it in GitHub Desktop.
Python script to implement bash completion (tested only on Ubuntu 18.04 and python 3.8).
"""
Example:
python3 complete.py define my_command
- Define this script as the handler in .bashrc
- Note: this will overwrite any previous handler
python3 complete.py undef my_command
- Remove it from .bashrc
python3 complete.py complete <current_word> <full_line>
- current_word is the index (starting with 1) of the word
- full_line is the full line, in a single argument
- This will print all the matching words on the standard output
Note:
- The header and footer should not be altered in any way
"""
import os
import shlex
'''
Variables containing COMP_ accessible when inside the completion function (
I might have forgot some variables that does not contain COMP_)
Those are not used in this script (except the COMP_CWORD and COMP_LINE), because
I either didn't find a use for them or I can't find their definition.
BASH_COMPLETION_VERSINFO=([0]="2" [1]="8")
COMP_CWORD=5
COMP_KEY=9
COMP_LINE='none ed '\'' est hi hi hi '\''h'\'' hd '\'' test "hi" '
COMP_POINT=48
COMP_TYPE=63
COMP_WORDBREAKS='
COMP_WORDS=([0]="none" [1]="ed" [2]="' est hi hi hi 'h' hd '" [3]="test" [4]="\"hi\"" [5]="")
'''
def complete(current_word, full_line):
split = shlex.split(full_line)
return list(i for i in list(i + "-level-" + current_word for i in ("test", "test-中文", 'test-한글')) if i.startswith(
"" if len(split) - 1 < int(current_word) else split.__getitem__(int(current_word))))
if __name__ == '__main__':
command = os.sys.argv.__getitem__(1)
if command in ('define', 'undef'):
definition = command == 'define'
alias = os.sys.argv.__getitem__(2)
"The next line is to make sure the command does not contain"
" strange characters or spaces"
assert shlex.quote(alias) == alias, (alias, shlex.quote(alias))
command_name = '__auto_complete_' + alias + '_function'
header = '# >>> ' + 'autocomplete definition of ' + alias + ' >>>'
footer = '# <<< ' + 'autocomplete definition of ' + alias + ' <<<'
if definition:
bash_script = f'''
{command_name} () {{
COMPREPLY=($({shlex.join((os.sys.executable, os.sys.argv.__getitem__(0)))} complete $COMP_CWORD "${{COMP_LINE}}"));
}};'''
aliasing = 'complete -F ' + command_name + ' ' + alias
warning = '# !!! Content defined withing this block is' \
' auto generated and might be deleted !!!'
content = '\n'.join((header, warning, bash_script, aliasing, footer))
else:
content = ''
bashrc_path = os.path.expanduser('~/.bashrc')
bashrc = open(bashrc_path).read()
def write_and_refresh(new_content: str):
while not new_content.endswith('\n'*3):
new_content += '\n'
os.rename(bashrc_path, bashrc_path + '.bak')
open(bashrc_path, 'x').write(new_content)
"subprocess.Popen(list(('source', bashrc_path)), shell=True).wait()"
try:
index = bashrc.index(header)
except ValueError:
try:
footer_index = bashrc.index(footer)
except ValueError:
if definition:
write_and_refresh(bashrc.rstrip() + '\n'*3 + content + '\n'*3)
else:
raise NameError('%r was not found' % (alias, ))
else:
raise RuntimeError('Corrupted bashrc: footer but no header')
else:
try:
footer_index = bashrc.index(footer)
except ValueError:
raise RuntimeError('Corrupted bashrc: header but no footer')
else:
old_content = bashrc.__getitem__(slice(index, footer_index + len(footer))).strip()
if old_content != content:
assert bashrc.count(old_content) == 1, 'This should not be happening'
write_and_refresh(bashrc.replace(old_content, content).rstrip())
elif command == 'complete':
os.sys.stdout.write(' '.join(complete(
*os.sys.argv.__getitem__(slice(2, None)))))
os.sys.stdout.flush()
else:
raise NotImplementedError(command)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment