Skip to content

Instantly share code, notes, and snippets.

@westurner westurner/parse_inputrc.py
Last active Sep 19, 2016

Embed
What would you like to do?
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

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.