Last active
September 19, 2016 15:19
-
-
Save westurner/0491f7e2c6d91842c3bcd3925d911ff7 to your computer and use it in GitHub Desktop.
parse a readline .inputrc file with Python
This file contains hidden or 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
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
from __future__ import print_function | |
""" | |
parse_inputrc -- parse a readline .inputrc file | |
.. note:: This probably only handles a subset of ``.inputrc`` syntax. | |
""" | |
import codecs | |
import collections | |
import functools | |
import json | |
import logging | |
import os | |
log = logging.getLogger() | |
class TextFile_(file): | |
def __init__(self, *args, **kwargs): | |
kwargs.setdefault('encoding', | |
os.environ.get('PYTHONIOENCODING', 'utf8')) | |
self.file = codecs.open(*args, **kwargs) | |
def __iter__(self): | |
"""strip whitespace, drop lines starting with #, and drop empty lines | |
""" | |
for line in self.file: | |
if line is None: | |
break | |
line = line.lstrip() | |
if line and not line[0] == '#': | |
yield line.rstrip() | |
def __enter__(self): | |
return self | |
def __exit__(self): | |
self.file.close() | |
return False | |
class OrderedDefaultDict(collections.OrderedDict): | |
def __init__(self, default_factory=None, *a, **kw): | |
self.default_factory = ( | |
default_factory if default_factory is not None else | |
functools.partial(self.__class__, default_factory=self.__class__)) | |
collections.OrderedDict.__init__(self, *a, **kw) | |
def __missing__(self, key): | |
self[key] = value = self.default_factory() | |
return value | |
class InputrcDict(OrderedDefaultDict): | |
def to_str_iter(self): | |
# yield collections.OrderedDict.__str__(self) | |
# return | |
print('# term\tapplication\tmode\tsubkey\tkey\tvalue') | |
for term, applicationitems in self.items(): | |
for application, modeitems in applicationitems.items(): | |
for mode, modekeyitems in modeitems.items(): | |
for subkey, subitems in modekeyitems.items(): | |
for key, value in subitems.items(): | |
yield unicode('\t').join(( | |
unicode(x) for x in [term, application, mode, subkey, key, value])) | |
def __str__(self): | |
return u'\n'.join(self.to_str_iter()) | |
def to_json(self): | |
return json.dumps(self, indent=2) | |
def parse_inputrc(filename=None): | |
"""parse a readline .inputrc file | |
Keyword Arguments: | |
filename (str or None): if None, defaults to ``~/.inputrc`` | |
Returns: | |
InputrcDict: InputrcDict OrderedDict | |
.. code:: python | |
mode in {None, 'vi', 'emacs'} | |
data = InputrcDict() # collection.OrderedDict() | |
data[term][application][mode]['settings']['setting_name'] = 'setting_value' | |
data[term][application][mode]['keybinds']['keyseq'] = 'command' | |
data[term][application][None]['commands']['command'] = [keyseq_0, keyseq_n] | |
""" | |
if filename is None: | |
raise ValueError('filename must be specified') | |
data = InputrcDict() | |
mode = None # None, emacs, vi | |
modestack = [None] | |
term = None | |
termstack = [None] | |
application = None | |
applicationstack = [None] | |
current_if = [] | |
for line in TextFile_(filename): | |
log.debug(['line', line]) | |
if line[0:4] == 'set ': | |
tokens = line[4:].split(None, 1) | |
key, value = tokens | |
key, value = key.strip(), value.strip() | |
log.debug(['set ', key, value]) | |
data[term][application][mode]['settings'][key] = value | |
if line[0:4] == '$if ': | |
if line[0:8] == '$if mode': | |
tokens = line[4:].split('=', 1) | |
_, mode = tokens | |
mode = mode.strip() | |
modestack.append(mode) | |
current_if.append('mode') | |
log.debug(['mode', mode]) | |
elif line[0:8] == '$if term': | |
tokens = line[4:].split('=', 1) | |
_, term = tokens | |
term = term.strip() | |
termstack.append(term) | |
current_if.append('term') | |
log.debug(['term', term]) | |
else: # line[0:4] == '$if ': | |
application = line[4:] | |
application = application.strip() | |
applicationstack.append(application) | |
current_if.append('application') | |
log.debug(['application', application]) | |
elif line[:5] == '$else' and current_if[-1] is not None: | |
if current_if[-1] == 'mode': | |
mode = '!' + modestack.pop() | |
modestack.append(mode) | |
log.debug(['mode', mode]) | |
elif current_if[-1] == 'term': | |
term = '!' + termstack.pop() | |
termstack.append(term) | |
log.debug(['term', term]) | |
elif current_if[-1] == 'application': | |
application = '!' + applicationstack.pop() | |
applicationstack.append(application) | |
log.debug(['application', application]) | |
elif line[:6] == '$endif' and current_if[-1] is not None: | |
if current_if[-1] == 'mode': | |
modestack.pop() | |
mode = modestack[-1] | |
current_if.pop() | |
log.debug(['mode', mode]) | |
elif current_if[-1] == 'term': | |
termstack.pop() | |
term = termstack[-1] | |
current_if.pop() | |
log.debug(['term', term]) | |
elif current_if[-1] == 'application': | |
applicationstack.pop() | |
application = applicationstack[-1] | |
current_if.pop() | |
log.debug(['application', application]) | |
elif ':' in line: | |
tokens = line.split(':', 1) | |
key, value = tokens | |
key, value = key, value.strip() | |
log.debug(['keyb', key, value]) | |
data[term][application][mode]['keybinds'][key] = value | |
commands = data[None][None][None]['commands'].setdefault(value, []) | |
commands.append(key) | |
commands = data[term][application][mode]['commands'].setdefault(value, []) | |
commands.append(key) | |
return data | |
import unittest | |
class Test_parse_inputrc(unittest.TestCase): | |
def setUp(self): | |
pass | |
@staticmethod | |
def get_test_filename(filename): | |
return os.path.join( | |
os.path.dirname(__file__), | |
#'tests', | |
filename) | |
def test_parse_inputrc(self): | |
filename = self.get_test_filename('test_inputrc-1') | |
data = parse_inputrc(filename=filename) | |
print(data) | |
self.assertIsInstance(data, InputrcDict) | |
self.assertIsInstance(data, collections.OrderedDict) | |
self.assertTrue(len(data)) | |
term, application, mode = None, None, None | |
mode_None = data[term][application][mode] | |
self.assertIsInstance(mode_None, InputrcDict) | |
self.assertIsInstance(mode_None, collections.OrderedDict) | |
self.assertTrue(len(mode_None)) | |
self.assertIn('settings', mode_None) | |
self.assertIn('commands', mode_None) | |
# self.assertIn('keybinds', mode_None) | |
for modestr in ['vi', 'emacs']: | |
mode = data[term][application][modestr] | |
self.assertIsInstance(mode, InputrcDict) | |
self.assertIsInstance(mode, collections.OrderedDict) | |
self.assertTrue(len(mode)) | |
self.assertIn('settings', mode) | |
self.assertIn('commands', mode) | |
self.assertIn('keybinds', mode) | |
raise Exception() | |
def tearDown(self): | |
pass | |
def main(argv=None): | |
""" | |
parse_inputrc main function | |
Keyword Arguments: | |
argv (list): commandline arguments (e.g. sys.argv[1:]) | |
Returns: | |
int: zero | |
""" | |
import logging | |
import optparse | |
prs = optparse.OptionParser(usage="%prog : args") | |
prs.add_option('-f', '--filename', | |
dest='filename', | |
action='store', | |
default=None, | |
help='Path to an .inputrc file (default: ~/.inputrc)') | |
prs.add_option('-v', '--verbose', | |
dest='verbose', | |
action='store_true',) | |
prs.add_option('-q', '--quiet', | |
dest='quiet', | |
action='store_true',) | |
prs.add_option('-t', '--test', | |
dest='run_tests', | |
action='store_true',) | |
(opts, args) = prs.parse_args(args=argv) | |
loglevel = logging.INFO | |
if opts.verbose: | |
loglevel = logging.DEBUG | |
elif opts.quiet: | |
loglevel = logging.ERROR | |
logging.basicConfig(level=loglevel) | |
argv = list(argv) if argv else [] | |
log = logging.getLogger() | |
log.setLevel(loglevel) | |
log.debug('argv: %r', argv) | |
log.debug('opts: %r', opts) | |
log.debug('args: %r', args) | |
if opts.run_tests: | |
import sys | |
sys.argv = [sys.argv[0]] + args | |
import unittest | |
return unittest.main() | |
if opts.filename is None: | |
opts.filename = os.path.expanduser(os.path.join('~', '.inputrc')) | |
EX_OK = 0 | |
output = parse_inputrc(filename=opts.filename) | |
print(output) | |
return EX_OK | |
if __name__ == "__main__": | |
import sys | |
sys.exit(main(argv=sys.argv[1:])) |
This file contains hidden or 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
### .inputrc -- readline configuration | |
## Bash readline quickstart | |
# https://www.gnu.org/software/bash/manual/html_node/Command-Line-Editing.html#Command-Line-Editing | |
# * https://www.gnu.org/software/bash/manual/html_node/Readline-Interaction.html | |
# * https://www.gnu.org/software/bash/manual/html_node/Readline-Init-File.html | |
# * https://www.gnu.org/software/bash/manual/html_node/Readline-vi-Mode.html#Readline-vi-Mode | |
# * https://github.com/whiteinge/dotfiles/blob/master/.inputrc | |
# | |
# help bind | |
# # list bindings | |
# bind -p | |
# bind -P | grep -v 'is not bound' | |
# # read bindings | |
# bind -f ~/.inputrc | |
# | |
set bell-style none | |
set page-completions on | |
set completion-ignore-case on | |
set completion-query-items 200 | |
set editing-mode vi | |
$if mode=vi | |
set testvalue vi | |
"\C-l_vi": clear-screen | |
Control-l: clear-screen | |
Control-L : clear-screen | |
$else | |
set testvalue !vi | |
"\C-l_!vi": clear-screen | |
$endif | |
$if mode=emacs | |
set testvalue emacs | |
"\C-l_emacs": clear-screen | |
$else | |
set testvalue !emacs | |
"\C-l_!emacs": clear-screen | |
$endif | |
$if Bash | |
set testvalue Bash | |
"\C-l_Bash": clear-screen | |
$else | |
set testvalue !Bash | |
"\C-l_!Bash": clear-screen | |
$endif | |
$if term=VT100 | |
set testvalue VT100 | |
"\C-l_VT100": clear-screen | |
$else | |
set testvalue !VT100 | |
"\C-l_!VT100": clear-screen | |
$endif | |
$if mode=vi | |
set testvalue vi2 | |
"\C-l_vi2": clear-screen | |
$if Bash | |
set testvalue Bash_vi | |
"\C-l_Bash_vi": clear-screen | |
$if term=VT100 | |
set testvalue Bash_vi_VT100 | |
"\C-l_Bash_vi_VT100": clear-screen | |
$else | |
set testvalue !VT100_Bash_vi | |
"\C-l_!VT100_Bash_vi": clear-screen | |
$endif | |
$else | |
set testvalue vi_!Bash | |
"\C-l_vi_!Bash": clear-screen | |
$endif | |
$else | |
set testvalue !vi2 | |
"\C-l_!vi2": clear-screen | |
$endif | |
$if Bash | |
$else | |
$if Python | |
$else | |
set testvalue !Bash_!Python | |
"\C-l_!Bash_!Python": clear-screen | |
$endif | |
$endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
see: https://github.com/jonathanslenders/python-prompt-toolkit/blob/master/LICENSE