From 8f757d7735fb798e4d119527fd6df50ea3eaaf80 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Thu, 31 Jan 2019 16:45:28 -0500 Subject: [PATCH] Refactored (1) Performed a general refactoring, focusing on the following: - Removing layers of abstraction in config handling - Stubbing out proper config validator - Updating envvar names located throughout the project --- bin/cheat | 5 +- cheat/configuration.py | 181 +++++++++++++++++------------------------ cheat/sheets.py | 21 ++--- cheat/utils.py | 22 ++--- config/cheat | 6 +- 5 files changed, 99 insertions(+), 136 deletions(-) diff --git a/bin/cheat b/bin/cheat index 252e52a..5e90ec4 100755 --- a/bin/cheat +++ b/bin/cheat @@ -43,13 +43,16 @@ from cheat.configuration import Configuration from docopt import docopt import os - if __name__ == '__main__': # parse the command-line options options = docopt(__doc__, version='cheat 2.4.2') + # initialize and validate configs config = Configuration() + config.validate() + + # bootsrap sheets = Sheets(config) utils = Utils(config) sheet = Sheet(sheets, utils) diff --git a/cheat/configuration.py b/cheat/configuration.py index 1700624..da3ed7d 100644 --- a/cheat/configuration.py +++ b/cheat/configuration.py @@ -2,133 +2,98 @@ import os from cheat.utils import Utils import json - class Configuration: def __init__(self): - self._get_global_conf_file_path() - self._get_local_conf_file_path() - self._saved_configuration = self._get_configuration() - - def _get_configuration(self): - # get options from config files and environment vairables - merged_config = {} + # compute the location of the config files + config_file_path_global = os.environ.get('CHEAT_GLOBAL_CONF_PATH') \ + or '/etc/cheat' + config_file_path_local = (os.environ.get('CHEAT_LOCAL_CONF_PATH') \ + or os.path.expanduser('~/.config/cheat/cheat')) + # attempt to read the global config file + config = {} try: - merged_config.update( - self._read_configuration_file(self.glob_config_path) - ) + config.update(self._read_config_file(config_file_path_global)) except Exception as e: - Utils.warn('error while parsing global configuration Reason: ' - + e.message - ) + Utils.warn('Error while parsing global configuration: ' + e.message) + # attempt to read the local config file try: - merged_config.update( - self._read_configuration_file(self.local_config_path) - ) + config.update(self._read_config_file(config_file_path_local)) except Exception as e: - Utils.warn('error while parsing user configuration Reason: ' - + e.message - ) + Utils.warn('Error while parsing local configuration: ' + e.message) - merged_config.update(self._read_env_vars_config()) + # With config files read, now begin to apply envvar overrides and + # default values - self._check_configuration(merged_config) + # self.cheat_colors + self.cheat_colors = self._select([ + os.environ.get('CHEAT_COLORS'), + os.environ.get('CHEATCOLORS'), + config.get('CHEAT_COLORS'), + True, + ]) + # convert strings to bool as necessary + if (isinstance(self.cheat_colors, str)): + self.cheat_colors = True \ + if self.cheat_colors.strip().lower() == 'true' \ + else False - return merged_config + # self.cheat_default_dir + self.cheat_default_dir = self._select([ + os.environ.get('CHEAT_DEFAULT_DIR'), + os.environ.get('DEFAULT_CHEAT_DIR'), + '~/.cheat', + ]) - def _read_configuration_file(self, path): + # self.cheat_editor + self.cheat_editor = self._select([ + os.environ.get('CHEAT_EDITOR'), + os.environ.get('EDITOR'), + os.environ.get('VISUAL'), + config.get('CHEAT_EDITOR'), + 'vi', + ]) + + # self.cheat_highlight + self.cheat_highlight = self._select([ + os.environ.get('CHEAT_HIGHLIGHT'), + config.get('CHEAT_HIGHLIGHT'), + False, + ]) + + # self.cheat_path + self.cheat_path = self._select([ + os.environ.get('CHEAT_PATH'), + os.environ.get('CHEATPATH'), + config.get('CHEAT_PATH'), + '/usr/share/cheat', + ]) + + def _read_config_file(self, path): # Reads configuration file and returns list of set variables - read_config = {} + config = {} if (os.path.isfile(path)): with open(path) as config_file: - read_config.update(json.load(config_file)) - return read_config + config.update(json.load(config_file)) + return config - def _read_env_vars_config(self): - read_config = {} + def _select(self, values): + for v in values: + if v is not None: + return v - # NOTE: These variables are left here because of backwards - # compatibility and are supported only as env vars but not in - # configuration file + def validate(self): + """ Validate configuration parameters """ - if (os.environ.get('VISUAL')): - read_config['EDITOR'] = os.environ.get('VISUAL') - - # variables supported both in environment and configuration file - # NOTE: Variables without CHEAT_ prefix are legacy - # key is variable name and value is its legacy_alias - # if variable has no legacy alias then set to None - variables = {'CHEAT_DEFAULT_DIR': 'DEFAULT_CHEAT_DIR', - 'CHEAT_PATH': 'CHEATPATH', - 'CHEAT_COLORS': 'CHEATCOLORS', - 'CHEAT_EDITOR': 'EDITOR', - 'CHEAT_HIGHLIGHT': None - } - - for (k, v) in variables.items(): - self._read_env_var(read_config, k, v) - - return read_config - - def _check_configuration(self, config): - """ Check values in config and warn user or die """ - - # validate CHEAT_HIGHLIGHT values if set - colors = [ + # assert that cheat_highlight contains a valid value + highlights = [ 'grey', 'red', 'green', 'yellow', - 'blue', 'magenta', 'cyan', 'white' + 'blue', 'magenta', 'cyan', 'white', + False ] - if ( - config.get('CHEAT_HIGHLIGHT') and - config.get('CHEAT_HIGHLIGHT') not in colors - ): - Utils.die("%s %s" % ('CHEAT_HIGHLIGHT must be one of:', colors)) + if (self.cheat_highlight not in highlights): + Utils.die("%s %s" % ('CHEAT_HIGHLIGHT must be one of:', highlights)) - def _read_env_var(self, current_config, key, alias=None): - if os.environ.get(key) is not None: - current_config[key] = os.environ.get(key) - return - elif alias is not None and os.environ.get(alias) is not None: - current_config[key] = os.environ.get(alias) - return - - def _get_global_conf_file_path(self): - self.glob_config_path = (os.environ.get('CHEAT_GLOBAL_CONF_PATH') - or '/etc/cheat') - - def _get_local_conf_file_path(self): - path = (os.environ.get('CHEAT_LOCAL_CONF_PATH') - or os.path.expanduser('~/.config/cheat/cheat')) - self.local_config_path = path - - def _choose_value(self, primary_value_name, secondary_value_name): - """ Return primary or secondary value in saved_configuration - - If primary value is in configuration then return it. If it is not - then return secondary. In the absence of both values return None - """ - - primary_value = self._saved_configuration.get(primary_value_name) - secondary_value = self._saved_configuration.get(secondary_value_name) - - if primary_value is not None: - return primary_value - else: - return secondary_value - - def get_default_cheat_dir(self): - return self._choose_value('CHEAT_DEFAULT_DIR', 'DEFAULT_CHEAT_DIR') - - def get_cheatpath(self): - return self._choose_value('CHEAT_PATH', 'CHEATPATH') - - def get_cheatcolors(self): - return self._choose_value('CHEAT_COLORS', 'CHEATCOLORS') - - def get_editor(self): - return self._choose_value('CHEAT_EDITOR', 'EDITOR') - - def get_highlight(self): - return self._saved_configuration.get('CHEAT_HIGHLIGHT') + return True diff --git a/cheat/sheets.py b/cheat/sheets.py index 201c87d..f45c225 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -7,20 +7,21 @@ from cheat.utils import Utils class Sheets: def __init__(self, config): - self._default_cheat_dir = config.get_default_cheat_dir() - self._cheatpath = config.get_cheatpath() + self._config = config self._utils = Utils(config) def default_path(self): """ Returns the default cheatsheet path """ # determine the default cheatsheet dir - default_sheets_dir = (self._default_cheat_dir or + # TODO: should probably rename `CHEAT_DEFAULT_DIR` to + # `CHEAT_USER_DIR` or something for clarity. + default_sheets_dir = (self._config.cheat_default_dir or os.path.join('~', '.cheat')) default_sheets_dir = os.path.expanduser( os.path.expandvars(default_sheets_dir)) - # create the DEFAULT_CHEAT_DIR if it does not exist + # create the CHEAT_DEFAULT_DIR if it does not exist if not os.path.isdir(default_sheets_dir): try: # @kludge: unclear on why this is necessary @@ -28,15 +29,15 @@ class Sheets: os.mkdir(default_sheets_dir) except OSError: - Utils.die('Could not create DEFAULT_CHEAT_DIR') + Utils.die('Could not create CHEAT_DEFAULT_DIR') - # assert that the DEFAULT_CHEAT_DIR is readable and writable + # assert that the CHEAT_DEFAULT_DIR is readable and writable if not os.access(default_sheets_dir, os.R_OK): - Utils.die('The DEFAULT_CHEAT_DIR (' + Utils.die('The CHEAT_DEFAULT_DIR (' + default_sheets_dir + ') is not readable.') if not os.access(default_sheets_dir, os.W_OK): - Utils.die('The DEFAULT_CHEAT_DIR (' + Utils.die('The CHEAT_DEFAULT_DIR (' + default_sheets_dir + ') is not writable.') @@ -67,8 +68,8 @@ class Sheets: ] # merge the CHEATPATH paths into the sheet_paths - if self._cheatpath: - for path in self._cheatpath.split(os.pathsep): + if self._config.cheat_path: + for path in self._config.cheat_path.split(os.pathsep): if os.path.isdir(path): sheet_paths.append(path) diff --git a/cheat/utils.py b/cheat/utils.py index e9d79e9..f1a46a3 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -7,15 +7,13 @@ import sys class Utils: def __init__(self, config): - self._displaycolors = config.get_cheatcolors() - self._editor_executable = config.get_editor() - self._highlight_color = config.get_highlight() + self._config = config def highlight(self, needle, haystack): """ Highlights a search term matched within a line """ # if a highlight color is not configured, exit early - if not self._highlight_color: + if not self._config.cheat_highlight: return haystack # otherwise, attempt to import the termcolor library @@ -27,18 +25,14 @@ class Utils: return haystack # if the import succeeds, colorize the needle in haystack - return haystack.replace(needle, colored(needle, self._highlight_color)) + return haystack.replace(needle, colored(needle, self._config.cheat_highlight)) def colorize(self, sheet_content): """ Colorizes cheatsheet content if so configured """ - # cover all possible positive values to be safe - positive_values = ["True", "true", "1", 1, True] - - # only colorize if configured to do so, and if stdout is a tty - if (self._displaycolors not in positive_values or - not sys.stdout.isatty()): - return sheet_content + # only colorize if cheat_colors is true, and stdout is a tty + if (self._config.cheat_colors is False or not sys.stdout.isatty()): + return sheet_content # don't attempt to colorize an empty cheatsheet if not sheet_content.strip(): @@ -78,13 +72,13 @@ class Utils: """ Determines the user's preferred editor """ # assert that the editor is set - if (not self._editor_executable): + if (not self._config.cheat_editor): Utils.die( 'You must set a CHEAT_EDITOR, VISUAL, or EDITOR environment ' 'variable or setting in order to create/edit a cheatsheet.' ) - return self._editor_executable + return self._config.cheat_editor def open_with_editor(self, filepath): """ Open `filepath` using the EDITOR specified by the env variables """ diff --git a/config/cheat b/config/cheat index fb1db33..bb18291 100644 --- a/config/cheat +++ b/config/cheat @@ -1,5 +1,5 @@ { - "CHEAT_COLORS": true, - "CHEAT_EDITOR":"vi", - "CHEAT_PATH":"/usr/share/cheat" + "CHEAT_COLORS" : true, + "CHEAT_EDITOR" : "vi", + "CHEAT_PATH" : "/usr/share/cheat" }