# encoding: utf-8
from __future__ import unicode_literals, absolute_import
import os
import sys
import locale
from itertools import chain
from six import iterkeys, iteritems
from six.moves.configparser import ConfigParser
from .autocomplete import SIMPLE as default_completion, ALL_MODES
class Struct(object):
"""Simple class for instantiating objects we can add arbitrary attributes
to and use for various arbitrary things."""
def getpreferredencoding():
"""Get the user's preferred encoding."""
return locale.getpreferredencoding() or sys.getdefaultencoding()
def can_encode(c):
try:
c.encode(getpreferredencoding())
return True
except UnicodeEncodeError:
return False
def supports_box_chars():
"""Check if the encoding supports Unicode box characters."""
return all(map(can_encode, "│─└┘┌┐"))
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 iterkeys(default_values):
if not config.has_section(section):
config.add_section(section)
for (opt, val) in iteritems(default_values[section]):
if not config.has_option(section, opt):
config.set(section, opt, "%s" % (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,
"autocomplete_mode": default_completion,
"color_scheme": "default",
"complete_magic_methods": True,
"dedent_after": 1,
"default_autoreload": False,
"editor": os.environ.get("VISUAL", os.environ.get("EDITOR", "vi")),
"flush_output": True,
"highlight_show_source": True,
"hist_duplicates": True,
"hist_file": "~/.pythonhist",
"hist_length": 1000,
"paste_time": 0.02,
"pastebin_confirm": True,
"pastebin_expiry": "1week",
"pastebin_helper": "",
"pastebin_url": "https://bpaste.net",
"save_append_py": False,
"single_undo_time": 1.0,
"syntax": True,
"tab_length": 4,
"unicode_box": True,
},
"keyboard": {
"backspace": "C-h",
"beginning_of_line": "C-a",
"clear_line": "C-u",
"clear_screen": "C-l",
"clear_word": "C-w",
"copy_clipboard": "F10",
"cut_to_buffer": "C-k",
"delete": "C-d",
"down_one_line": "C-n",
"edit_config": "F3",
"edit_current_block": "C-x",
"end_of_line": "C-e",
"exit": "",
"external_editor": "F7",
"help": "F1",
"incremental_search": "M-s",
"last_output": "F9",
"left": "C-b",
"pastebin": "F8",
"redo": "C-g",
"reimport": "F6",
"reverse_incremental_search": "M-r",
"right": "C-f",
"save": "C-s",
"search": "C-o",
"show_source": "F2",
"suspend": "C-z",
"toggle_file_watch": "F5",
"transpose_chars": "C-t",
"undo": "C-r",
"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,},
}
default_keys_to_commands = dict(
(value, key) for (key, value) in iteritems(defaults["keyboard"])
)
fill_config_with_default_values(config, defaults)
try:
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)
except UnicodeDecodeError as e:
sys.stderr.write(
"Error: Unable to parse config file at '{}' due to an "
"encoding issue. Please make sure to fix the encoding "
"of the file or remove it and then try again.\n".format(config_path)
)
sys.exit(1)
def get_key_no_doublebind(command):
default_commands_to_keys = defaults["keyboard"]
requested_key = config.get("keyboard", command)
try:
default_command = default_keys_to_commands[requested_key]
if default_commands_to_keys[default_command] == config.get(
"keyboard", default_command
):
setattr(struct, "%s_key" % default_command, "")
except KeyError:
pass
return requested_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.single_undo_time = config.getfloat("general", "single_undo_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.default_autoreload = config.getboolean(
"general", "default_autoreload"
)
struct.pastebin_key = get_key_no_doublebind("pastebin")
struct.copy_clipboard_key = get_key_no_doublebind("copy_clipboard")
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.redo_key = get_key_no_doublebind("redo")
struct.reimport_key = get_key_no_doublebind("reimport")
struct.reverse_incremental_search_key = get_key_no_doublebind(
"reverse_incremental_search"
)
struct.incremental_search_key = get_key_no_doublebind("incremental_search")
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.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_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)
# expand path of history file
struct.hist_file = os.path.expanduser(struct.hist_file)
# verify completion mode
if struct.autocomplete_mode not in ALL_MODES:
struct.autocomplete_mode = default_completion
# set box drawing characters
if config.getboolean("general", "unicode_box") and supports_box_chars():
struct.left_border = "│"
struct.right_border = "│"
struct.top_border = "─"
struct.bottom_border = "─"
struct.left_bottom_corner = "└"
struct.right_bottom_corner = "┘"
struct.left_top_corner = "┌"
struct.right_top_corner = "┐"
else:
struct.left_border = "|"
struct.right_border = "|"
struct.top_border = "-"
struct.bottom_border = "-"
struct.left_bottom_corner = "+"
struct.right_bottom_corner = "+"
struct.left_top_corner = "+"
struct.right_top_corner = "+"
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 iteritems(default_colors):
if k not in colors:
colors[k] = v