From cba203b379db36712c89fde7b05a460cae3a8020 Mon Sep 17 00:00:00 2001 From: Thomas Roten Date: Fri, 13 Nov 2015 21:40:26 -0600 Subject: [PATCH 1/9] Adds system-wide mycli config file. --- mycli/config.py | 35 +++++++++++++++++------ mycli/main.py | 24 +++++++++++----- mycli/packages/special/favoritequeries.py | 5 ++-- 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/mycli/config.py b/mycli/config.py index 8e9d9d62..8429cc96 100644 --- a/mycli/config.py +++ b/mycli/config.py @@ -2,9 +2,9 @@ from io import BytesIO, TextIOWrapper import logging import os -from os.path import expanduser, exists +from os.path import exists import struct -from configobj import ConfigObj +from configobj import ConfigObj, ConfigObjError try: from Crypto.Cipher import AES except ImportError: @@ -19,16 +19,33 @@ class CryptoError(Exception): logger = logging.getLogger(__name__) -def load_config(usr_cfg, def_cfg=None): - cfg = ConfigObj() - cfg.merge(ConfigObj(def_cfg, interpolation=False)) - cfg.merge(ConfigObj(expanduser(usr_cfg), interpolation=False)) - cfg.filename = expanduser(usr_cfg) +def read_config_files(files, base_config=None): + """Read and merge a string or list of config files. - return cfg + If a file is read successfully, the config object takes on that + filename. + """ + + config = base_config or ConfigObj() + files = [files] if isinstance(files, str) else files + + for _file in files: + try: + _config = ConfigObj(_file, interpolation=False) + if bool(_config) is True: + config.filename = _file + config.merge(_config) + except ConfigObjError as e: + logger.error("Error parsing config file '{0}'.".format(_file)) + logger.error('Recovering partially parsed config values.') + config.merge(e.config) + except (IOError, PermissionError) as e: + logger.warning("You don't have permission to read config " + "file' {0}'.".format(e.filename)) + + return config def write_default_config(source, destination, overwrite=False): - destination = expanduser(destination) if not overwrite and exists(destination): return diff --git a/mycli/main.py b/mycli/main.py index 815d3439..698b0b94 100755 --- a/mycli/main.py +++ b/mycli/main.py @@ -35,8 +35,8 @@ from .sqlexecute import SQLExecute from .clibuffer import CLIBuffer from .completion_refresher import CompletionRefresher -from .config import (write_default_config, load_config, get_mylogin_cnf_path, - open_mylogin_cnf, CryptoError) +from .config import (write_default_config, get_mylogin_cnf_path, + open_mylogin_cnf, CryptoError, read_config_files) from .key_bindings import mycli_bindings from .encodingutils import utf8tounicode from .lexer import MyCliLexer @@ -70,6 +70,14 @@ class MyCli(object): os.path.expanduser('~/.my.cnf') ] + system_config_files = [ + '/etc/myclirc', + ] + + default_config_file = os.path.join(PACKAGE_ROOT, 'myclirc') + user_config_file = os.path.expanduser('~/.myclirc') + + def __init__(self, sqlexecute=None, prompt=None, logfile=None, defaults_suffix=None, defaults_file=None, login_path=None): @@ -85,12 +93,10 @@ def __init__(self, sqlexecute=None, prompt=None, if defaults_file: self.cnf_files = [defaults_file] - default_config = os.path.join(PACKAGE_ROOT, 'myclirc') - write_default_config(default_config, '~/.myclirc') - - # Load config. - c = self.config = load_config('~/.myclirc', default_config) + c = self.config = ConfigObj(self.default_config_file) + read_config_files(self.system_config_files, base_config=c) + read_config_files(self.user_config_file, base_config=c) self.multi_line = c['main'].as_bool('multi_line') self.destructive_warning = c['main'].as_bool('destructive_warning') self.key_bindings = c['main']['key_bindings'] @@ -100,6 +106,10 @@ def __init__(self, sqlexecute=None, prompt=None, self.cli_style = c['colors'] self.wider_completion_menu = c['main'].as_bool('wider_completion_menu') + # Write user config if system config wasn't the last config loaded. + if c.filename not in self.system_config_files: + write_default_config(self.default_config_file, self.user_config_file) + # audit log if self.logfile is None and 'audit_log' in c['main']: try: diff --git a/mycli/packages/special/favoritequeries.py b/mycli/packages/special/favoritequeries.py index addd1fe7..3e88c7ca 100644 --- a/mycli/packages/special/favoritequeries.py +++ b/mycli/packages/special/favoritequeries.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +from os.path import expanduser class FavoriteQueries(object): @@ -57,5 +58,5 @@ def delete(self, name): self.config.write() return '%s: Deleted' % name -from ...config import load_config -favoritequeries = FavoriteQueries(load_config('~/.myclirc')) +from ...config import read_config_files +favoritequeries = FavoriteQueries(read_config_files(expanduser('~/.myclirc'))) From 8864857a0bf0496129db6cb1d1b497b1ccddd260 Mon Sep 17 00:00:00 2001 From: Thomas Roten Date: Fri, 13 Nov 2015 21:46:46 -0600 Subject: [PATCH 2/9] Removes redendant cnf reading code. --- mycli/main.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/mycli/main.py b/mycli/main.py index 698b0b94..38400564 100755 --- a/mycli/main.py +++ b/mycli/main.py @@ -251,15 +251,7 @@ def read_my_cnf_files(self, files, keys): :param keys: list of keys to retrieve :returns: tuple, with None for missing keys. """ - cnf = ConfigObj() - for _file in files: - try: - cnf.merge(ConfigObj(_file, interpolation=False)) - except ConfigObjError as e: - self.logger.error('Error parsing %r.', _file) - self.logger.error('Recovering partially parsed config values.') - cnf.merge(e.config) - pass + cnf = read_config_files(files) sections = ['client'] if self.login_path and self.login_path != 'client': From d9d64ce78e8fad358cf686f5cf486c33c3ab2f33 Mon Sep 17 00:00:00 2001 From: Thomas Roten Date: Fri, 13 Nov 2015 22:15:32 -0600 Subject: [PATCH 3/9] Creates a read_config_file function. --- mycli/config.py | 43 +++++++++++++---------- mycli/main.py | 5 +-- mycli/packages/special/favoritequeries.py | 4 +-- 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/mycli/config.py b/mycli/config.py index 8429cc96..931b2711 100644 --- a/mycli/config.py +++ b/mycli/config.py @@ -19,29 +19,36 @@ class CryptoError(Exception): logger = logging.getLogger(__name__) -def read_config_files(files, base_config=None): - """Read and merge a string or list of config files. +def read_config_file(f, base_config=None): + """Read and merge a config file. - If a file is read successfully, the config object takes on that - filename. + If the file is read successfully, the config object takes + on that filename. """ - config = base_config or ConfigObj() - files = [files] if isinstance(files, str) else files + config = ConfigObj() if base_config is None else base_config + try: + _config = ConfigObj(f, interpolation=False) + config.merge(_config) + if bool(_config) is True: + config.filename = f + except ConfigObjError as e: + logger.error("Error parsing config file '{0}'.".format(f)) + logger.error('Recovering partially parsed config values.') + config.merge(e.config) + except (IOError, PermissionError) as e: + logger.warning("You don't have permission to read config " + "file '{0}'.".format(e.filename)) + + return config + +def read_config_files(files, base_config=None): + """Read and merge a list of config files.""" + + config = ConfigObj() if base_config is None else base_config for _file in files: - try: - _config = ConfigObj(_file, interpolation=False) - if bool(_config) is True: - config.filename = _file - config.merge(_config) - except ConfigObjError as e: - logger.error("Error parsing config file '{0}'.".format(_file)) - logger.error('Recovering partially parsed config values.') - config.merge(e.config) - except (IOError, PermissionError) as e: - logger.warning("You don't have permission to read config " - "file' {0}'.".format(e.filename)) + read_config_file(_file, base_config=config) return config diff --git a/mycli/main.py b/mycli/main.py index 38400564..19d51a9e 100755 --- a/mycli/main.py +++ b/mycli/main.py @@ -36,7 +36,8 @@ from .clibuffer import CLIBuffer from .completion_refresher import CompletionRefresher from .config import (write_default_config, get_mylogin_cnf_path, - open_mylogin_cnf, CryptoError, read_config_files) + open_mylogin_cnf, CryptoError, read_config_file, + read_config_files) from .key_bindings import mycli_bindings from .encodingutils import utf8tounicode from .lexer import MyCliLexer @@ -96,7 +97,7 @@ def __init__(self, sqlexecute=None, prompt=None, # Load config. c = self.config = ConfigObj(self.default_config_file) read_config_files(self.system_config_files, base_config=c) - read_config_files(self.user_config_file, base_config=c) + read_config_file(self.user_config_file, base_config=c) self.multi_line = c['main'].as_bool('multi_line') self.destructive_warning = c['main'].as_bool('destructive_warning') self.key_bindings = c['main']['key_bindings'] diff --git a/mycli/packages/special/favoritequeries.py b/mycli/packages/special/favoritequeries.py index 3e88c7ca..a8bd6d41 100644 --- a/mycli/packages/special/favoritequeries.py +++ b/mycli/packages/special/favoritequeries.py @@ -58,5 +58,5 @@ def delete(self, name): self.config.write() return '%s: Deleted' % name -from ...config import read_config_files -favoritequeries = FavoriteQueries(read_config_files(expanduser('~/.myclirc'))) +from ...config import read_config_file +favoritequeries = FavoriteQueries(read_config_file(expanduser('~/.myclirc'))) From 68aad93dcc8aad47ff4aa9978cd734631b83e47f Mon Sep 17 00:00:00 2001 From: Thomas Roten Date: Fri, 13 Nov 2015 22:17:09 -0600 Subject: [PATCH 4/9] Makes reading default config use read_config_file function. --- mycli/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mycli/main.py b/mycli/main.py index 19d51a9e..fecf2805 100755 --- a/mycli/main.py +++ b/mycli/main.py @@ -95,7 +95,7 @@ def __init__(self, sqlexecute=None, prompt=None, self.cnf_files = [defaults_file] # Load config. - c = self.config = ConfigObj(self.default_config_file) + c = self.config = read_config_file(self.default_config_file) read_config_files(self.system_config_files, base_config=c) read_config_file(self.user_config_file, base_config=c) self.multi_line = c['main'].as_bool('multi_line') From 0f843c0b0eced191f2caee00531bc8a570bc2559 Mon Sep 17 00:00:00 2001 From: Thomas Roten Date: Sat, 14 Nov 2015 14:07:13 -0600 Subject: [PATCH 5/9] Removes the passed ConfigObj object from the config file functions. --- mycli/config.py | 28 ++++++++++++---------------- mycli/main.py | 6 +++--- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/mycli/config.py b/mycli/config.py index 931b2711..4f57d76d 100644 --- a/mycli/config.py +++ b/mycli/config.py @@ -19,36 +19,32 @@ class CryptoError(Exception): logger = logging.getLogger(__name__) -def read_config_file(f, base_config=None): - """Read and merge a config file. +def read_config_file(f): + """Read a config file.""" - If the file is read successfully, the config object takes - on that filename. - """ - - config = ConfigObj() if base_config is None else base_config try: - _config = ConfigObj(f, interpolation=False) - config.merge(_config) - if bool(_config) is True: - config.filename = f + config = ConfigObj(f, interpolation=False) except ConfigObjError as e: logger.error("Error parsing config file '{0}'.".format(f)) logger.error('Recovering partially parsed config values.') - config.merge(e.config) - except (IOError, PermissionError) as e: + return e.config + except (IOError, OSError) as e: logger.warning("You don't have permission to read config " "file '{0}'.".format(e.filename)) + return None return config -def read_config_files(files, base_config=None): +def read_config_files(files): """Read and merge a list of config files.""" - config = ConfigObj() if base_config is None else base_config + config = ConfigObj() for _file in files: - read_config_file(_file, base_config=config) + _config = read_config_file(_file) + if bool(_config) is True: + config.merge(_config) + config.filename = _file return config diff --git a/mycli/main.py b/mycli/main.py index fecf2805..768d94f2 100755 --- a/mycli/main.py +++ b/mycli/main.py @@ -95,9 +95,9 @@ def __init__(self, sqlexecute=None, prompt=None, self.cnf_files = [defaults_file] # Load config. - c = self.config = read_config_file(self.default_config_file) - read_config_files(self.system_config_files, base_config=c) - read_config_file(self.user_config_file, base_config=c) + config_files = ([self.default_config_file] + self.system_config_files + + [self.user_config_file]) + c = self.config = read_config_files(config_files) self.multi_line = c['main'].as_bool('multi_line') self.destructive_warning = c['main'].as_bool('destructive_warning') self.key_bindings = c['main']['key_bindings'] From b5819ca91d4a6be0073bbd3f52e042fe4632c748 Mon Sep 17 00:00:00 2001 From: Thomas Roten Date: Sat, 14 Nov 2015 14:39:32 -0600 Subject: [PATCH 6/9] Makes config errors print to stderr. --- mycli/config.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/mycli/config.py b/mycli/config.py index 4f57d76d..fe61aabc 100644 --- a/mycli/config.py +++ b/mycli/config.py @@ -1,9 +1,11 @@ +from __future__ import print_function import shutil from io import BytesIO, TextIOWrapper import logging import os from os.path import exists import struct +import sys from configobj import ConfigObj, ConfigObjError try: from Crypto.Cipher import AES @@ -25,12 +27,12 @@ def read_config_file(f): try: config = ConfigObj(f, interpolation=False) except ConfigObjError as e: - logger.error("Error parsing config file '{0}'.".format(f)) - logger.error('Recovering partially parsed config values.') + print("Error parsing config file '{0}'.".format(f), file=sys.stderr) + print('Recovering partially parsed config values.', file=sys.stderr) return e.config except (IOError, OSError) as e: - logger.warning("You don't have permission to read config " - "file '{0}'.".format(e.filename)) + print("You don't have permission to read config file " + "'{0}'.".format(e.filename), file=sys.stderr) return None return config From 4cc4a92d4659b52182a09b2b7940519b6b516702 Mon Sep 17 00:00:00 2001 From: Thomas Roten Date: Sat, 14 Nov 2015 14:46:44 -0600 Subject: [PATCH 7/9] Adds line number to broken config error message. --- mycli/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mycli/config.py b/mycli/config.py index fe61aabc..f650a9fd 100644 --- a/mycli/config.py +++ b/mycli/config.py @@ -27,7 +27,8 @@ def read_config_file(f): try: config = ConfigObj(f, interpolation=False) except ConfigObjError as e: - print("Error parsing config file '{0}'.".format(f), file=sys.stderr) + print("Error parsing line {0} of config file '{1}'.".format( + e.line_number, f), file=sys.stderr) print('Recovering partially parsed config values.', file=sys.stderr) return e.config except (IOError, OSError) as e: From 136c2b4b85e8ae897b0272e0eb13adf96eef80d8 Mon Sep 17 00:00:00 2001 From: Thomas Roten Date: Sun, 15 Nov 2015 18:36:39 -0600 Subject: [PATCH 8/9] Adds function for outputting config-related errors. --- mycli/config.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/mycli/config.py b/mycli/config.py index f650a9fd..0fc438c1 100644 --- a/mycli/config.py +++ b/mycli/config.py @@ -21,19 +21,27 @@ class CryptoError(Exception): logger = logging.getLogger(__name__) +def log(logger, level, message): + """Logs message to stderr if logging isn't initialized.""" + + if logger.parent.name != 'root': + logger.log(level, message) + else: + print(message, file=sys.stderr) + def read_config_file(f): """Read a config file.""" try: config = ConfigObj(f, interpolation=False) except ConfigObjError as e: - print("Error parsing line {0} of config file '{1}'.".format( - e.line_number, f), file=sys.stderr) - print('Recovering partially parsed config values.', file=sys.stderr) + log(logger, logging.ERROR, "Unable to parse line {0} of config file " + "'{1}'.".format(e.line_number, f)) + log(logger, logging.ERROR, "Using successfully parsed config values.") return e.config except (IOError, OSError) as e: - print("You don't have permission to read config file " - "'{0}'.".format(e.filename), file=sys.stderr) + log(logger, logging.WARNING, "You don't have permission to read " + "config file '{0}'.".format(e.filename)) return None return config From b1a5df139c3564cd7f237eb1ae0fa50a3c68b674 Mon Sep 17 00:00:00 2001 From: Thomas Roten Date: Sun, 15 Nov 2015 21:56:05 -0600 Subject: [PATCH 9/9] Makes config file functions call expanduser. --- mycli/config.py | 10 +++++++++- mycli/main.py | 4 ++-- mycli/packages/special/favoritequeries.py | 3 +-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/mycli/config.py b/mycli/config.py index 0fc438c1..0d0dc2e9 100644 --- a/mycli/config.py +++ b/mycli/config.py @@ -7,6 +7,10 @@ import struct import sys from configobj import ConfigObj, ConfigObjError +try: + basestring +except NameError: + basestring = str try: from Crypto.Cipher import AES except ImportError: @@ -32,6 +36,9 @@ def log(logger, level, message): def read_config_file(f): """Read a config file.""" + if isinstance(f, basestring): + f = os.path.expanduser(f) + try: config = ConfigObj(f, interpolation=False) except ConfigObjError as e: @@ -55,11 +62,12 @@ def read_config_files(files): _config = read_config_file(_file) if bool(_config) is True: config.merge(_config) - config.filename = _file + config.filename = _config.filename return config def write_default_config(source, destination, overwrite=False): + destination = os.path.expanduser(destination) if not overwrite and exists(destination): return diff --git a/mycli/main.py b/mycli/main.py index 768d94f2..ec8e6a82 100755 --- a/mycli/main.py +++ b/mycli/main.py @@ -68,7 +68,7 @@ class MyCli(object): '/etc/my.cnf', '/etc/mysql/my.cnf', '/usr/local/etc/my.cnf', - os.path.expanduser('~/.my.cnf') + '~/.my.cnf' ] system_config_files = [ @@ -76,7 +76,7 @@ class MyCli(object): ] default_config_file = os.path.join(PACKAGE_ROOT, 'myclirc') - user_config_file = os.path.expanduser('~/.myclirc') + user_config_file = '~/.myclirc' def __init__(self, sqlexecute=None, prompt=None, diff --git a/mycli/packages/special/favoritequeries.py b/mycli/packages/special/favoritequeries.py index a8bd6d41..bec14ecb 100644 --- a/mycli/packages/special/favoritequeries.py +++ b/mycli/packages/special/favoritequeries.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from os.path import expanduser class FavoriteQueries(object): @@ -59,4 +58,4 @@ def delete(self, name): return '%s: Deleted' % name from ...config import read_config_file -favoritequeries = FavoriteQueries(read_config_file(expanduser('~/.myclirc'))) +favoritequeries = FavoriteQueries(read_config_file('~/.myclirc'))