Skip to content

Instantly share code, notes, and snippets.

@westurner
Last active September 19, 2016 15:19
Show Gist options
  • Save westurner/0491f7e2c6d91842c3bcd3925d911ff7 to your computer and use it in GitHub Desktop.
Save westurner/0491f7e2c6d91842c3bcd3925d911ff7 to your computer and use it in GitHub Desktop.
parse a readline .inputrc file with Python
#!/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:]))
### .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
@westurner
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment