Created
January 5, 2020 20:04
-
-
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).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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