from __future__ import with_statement
import os
import sys
from ConfigParser import ConfigParser
from itertools import chain
from bpython.keys import cli_key_dispatch as key_dispatch
from bpython.autocomplete import SIMPLE as default_completion
class Struct(object):
"""Simple class for instantiating objects we can add arbitrary attributes
to and use for various arbitrary things."""
def get_config_home():
"""Returns the base directory for bpython's configuration files."""
xdg_config_home = os.environ.get('XDG_CONFIG_HOME', '~/.config')
return os.path.join(xdg_config_home, 'bpython')
def default_config_path():
"""Returns bpython's default configuration file path."""
return os.path.join(get_config_home(), 'config')
def fill_config_with_default_values(config, default_values):
for section in default_values.iterkeys():
if not config.has_section(section):
config.add_section(section)
for (opt, val) in default_values[section].iteritems():
if not config.has_option(section, opt):
config.set(section, opt, str(val))
def loadini(struct, configfile):
"""Loads .ini configuration file and stores its values in struct"""
config_path = os.path.expanduser(configfile)
config = ConfigParser()
defaults = {
'general': {
'arg_spec': True,
'auto_display_list': True,
'color_scheme': 'default',
'complete_magic_methods' : True,
'autocomplete_mode': default_completion,
'dedent_after': 1,
'flush_output': True,
'highlight_show_source': True,
'hist_file': '~/.pythonhist',
'hist_length': 100,
'hist_duplicates': True,
'paste_time': 0.02,
'syntax': True,
'tab_length': 4,
'pastebin_confirm': True,
'pastebin_url': 'https://bpaste.net/json/new',
'pastebin_show_url': 'https://bpaste.net/show/$paste_id',
'pastebin_removal_url': 'https://bpaste.net/remove/$removal_id',
'pastebin_expiry': '1week',
'pastebin_helper': '',
'save_append_py': False,
'editor': os.environ.get('VISUAL', os.environ.get('EDITOR', 'vi'))
},
'keyboard': {
'backspace': 'C-h',
'left': 'C-b',
'right': 'C-f',
'beginning_of_line': 'C-a',
'end_of_line': 'C-e',
'transpose_chars': 'C-t',
'clear_line': 'C-u',
'clear_screen': 'C-l',
'kill_line': 'C-k',
'clear_word': 'C-w',
'cut_to_buffer': 'C-k',
'delete': 'C-d',
'down_one_line': 'C-n',
'exit': '',
'external_editor': 'F7',
'edit_config': 'F3',
'edit_current_block': 'C-x',
'help': 'F1',
'last_output': 'F9',
'pastebin': 'F8',
'save': 'C-s',
'show_source': 'F2',
'suspend': 'C-z',
'toggle_file_watch': 'F5',
'undo': 'C-r',
'reimport': 'F6',
'search': 'C-o',
'up_one_line': 'C-p',
'yank_from_buffer': 'C-y'},
'cli': {
'suggestion_width': 0.8,
'trim_prompts': False,
},
'curtsies': {
'list_above' : False,
'right_arrow_completion' : True,
}}
fill_config_with_default_values(config, defaults)
if not config.read(config_path):
# No config file. If the user has it in the old place then complain
if os.path.isfile(os.path.expanduser('~/.bpython.ini')):
sys.stderr.write("Error: It seems that you have a config file at "
"~/.bpython.ini. Please move your config file to "
"%s\n" % default_config_path())
sys.exit(1)
def get_key_no_doublebind(attr, already_used={}):
"""Clears any other configured keybindings using this key"""
key = config.get('keyboard', attr)
if key in already_used:
default = defaults['keyboard'][already_used[key]]
if default in already_used:
setattr(struct, '%s_key' % already_used[key], '')
else:
setattr(struct, '%s_key' % already_used[key], default)
already_used[key] = attr
return key
struct.config_path = config_path
struct.dedent_after = config.getint('general', 'dedent_after')
struct.tab_length = config.getint('general', 'tab_length')
struct.auto_display_list = config.getboolean('general',
'auto_display_list')
struct.syntax = config.getboolean('general', 'syntax')
struct.arg_spec = config.getboolean('general', 'arg_spec')
struct.paste_time = config.getfloat('general', 'paste_time')
struct.highlight_show_source = config.getboolean('general',
'highlight_show_source')
struct.hist_file = config.get('general', 'hist_file')
struct.editor = config.get('general', 'editor')
struct.hist_length = config.getint('general', 'hist_length')
struct.hist_duplicates = config.getboolean('general', 'hist_duplicates')
struct.flush_output = config.getboolean('general', 'flush_output')
struct.pastebin_key = get_key_no_doublebind('pastebin')
struct.save_key = get_key_no_doublebind('save')
struct.search_key = get_key_no_doublebind('search')
struct.show_source_key = get_key_no_doublebind('show_source')
struct.suspend_key = get_key_no_doublebind('suspend')
struct.toggle_file_watch_key = get_key_no_doublebind('toggle_file_watch')
struct.undo_key = get_key_no_doublebind('undo')
struct.reimport_key = get_key_no_doublebind('reimport')
struct.up_one_line_key = get_key_no_doublebind('up_one_line')
struct.down_one_line_key = get_key_no_doublebind('down_one_line')
struct.cut_to_buffer_key = get_key_no_doublebind('cut_to_buffer')
struct.yank_from_buffer_key = get_key_no_doublebind('yank_from_buffer')
struct.clear_word_key = get_key_no_doublebind('clear_word')
struct.backspace_key = get_key_no_doublebind('backspace')
struct.clear_line_key = get_key_no_doublebind('clear_line')
struct.clear_screen_key = get_key_no_doublebind('clear_screen')
struct.delete_key = get_key_no_doublebind('delete')
struct.left_key = get_key_no_doublebind('left')
struct.right_key = get_key_no_doublebind('right')
struct.end_of_line_key = get_key_no_doublebind('end_of_line')
struct.beginning_of_line_key = get_key_no_doublebind('beginning_of_line')
struct.transpose_chars_key = get_key_no_doublebind('transpose_chars')
struct.clear_line_key = get_key_no_doublebind('clear_line')
struct.clear_screen_key = get_key_no_doublebind('clear_screen')
struct.kill_line_key = get_key_no_doublebind('kill_line')
struct.exit_key = get_key_no_doublebind('exit')
struct.last_output_key = get_key_no_doublebind('last_output')
struct.edit_config_key = get_key_no_doublebind('edit_config')
struct.edit_current_block_key = get_key_no_doublebind('edit_current_block')
struct.external_editor_key = get_key_no_doublebind('external_editor')
struct.help_key = get_key_no_doublebind('help')
struct.pastebin_confirm = config.getboolean('general', 'pastebin_confirm')
struct.pastebin_url = config.get('general', 'pastebin_url')
struct.pastebin_show_url = config.get('general', 'pastebin_show_url')
struct.pastebin_removal_url = config.get('general', 'pastebin_removal_url')
struct.pastebin_expiry = config.get('general', 'pastebin_expiry')
struct.pastebin_helper = config.get('general', 'pastebin_helper')
struct.cli_suggestion_width = config.getfloat('cli',
'suggestion_width')
struct.cli_trim_prompts = config.getboolean('cli',
'trim_prompts')
struct.complete_magic_methods = config.getboolean('general',
'complete_magic_methods')
struct.autocomplete_mode = config.get('general', 'autocomplete_mode')
struct.save_append_py = config.getboolean('general', 'save_append_py')
struct.curtsies_list_above = config.getboolean('curtsies', 'list_above')
struct.curtsies_right_arrow_completion = config.getboolean('curtsies', 'right_arrow_completion')
color_scheme_name = config.get('general', 'color_scheme')
default_colors = {
'keyword': 'y',
'name': 'c',
'comment': 'b',
'string': 'm',
'error': 'r',
'number': 'G',
'operator': 'Y',
'punctuation': 'y',
'token': 'C',
'background': 'd',
'output': 'w',
'main': 'c',
'paren': 'R',
'prompt': 'c',
'prompt_more': 'g',
'right_arrow_suggestion': 'K',
}
if color_scheme_name == 'default':
struct.color_scheme = default_colors
else:
struct.color_scheme = dict()
theme_filename = color_scheme_name + '.theme'
path = os.path.expanduser(os.path.join(get_config_home(),
theme_filename))
try:
load_theme(struct, path, struct.color_scheme, default_colors)
except EnvironmentError:
sys.stderr.write("Could not load theme '%s'.\n" %
(color_scheme_name, ))
sys.exit(1)
# checks for valid key configuration this part still sucks
for key in (struct.pastebin_key, struct.save_key):
key_dispatch[key]
def load_theme(struct, path, colors, default_colors):
theme = ConfigParser()
with open(path, 'r') as f:
theme.readfp(f)
for k, v in chain(theme.items('syntax'), theme.items('interface')):
if theme.has_option('syntax', k):
colors[k] = theme.get('syntax', k)
else:
colors[k] = theme.get('interface', k)
# Check against default theme to see if all values are defined
for k, v in default_colors.iteritems():
if k not in colors:
colors[k] = v
f.close()