From c4c935a6a5affc9b6c76ec5af73a297bc72ad365 Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Sat, 12 May 2018 17:19:16 +0200 Subject: [PATCH 01/10] Change default location of cheatsheets --- cheat/cheatsheets/__init__.py | 4 ---- cheat/sheets.py | 2 +- setup.py | 14 +++++++++----- 3 files changed, 10 insertions(+), 10 deletions(-) delete mode 100644 cheat/cheatsheets/__init__.py diff --git a/cheat/cheatsheets/__init__.py b/cheat/cheatsheets/__init__.py deleted file mode 100644 index 7a6d5a1..0000000 --- a/cheat/cheatsheets/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -import os - -def sheets_dir(): - return os.path.split(__file__) diff --git a/cheat/sheets.py b/cheat/sheets.py index 383de87..7a44342 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -53,7 +53,7 @@ def paths(): """ Assembles a list of directories containing cheatsheets """ sheet_paths = [ default_path(), - cheatsheets.sheets_dir()[0], + '/usr/share/cheat', ] # merge the CHEATPATH paths into the sheet_paths diff --git a/setup.py b/setup.py index b7aeee4..0bcfa1f 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,11 @@ from distutils.core import setup +import glob import os +cheat_files = [] +for f in os.listdir('cheat/cheatsheets/'): + cheat_files.append(os.path.join('cheat/cheatsheets/',f)) + setup( name = 'cheat', version = '2.3.1', @@ -14,15 +19,14 @@ setup( url = 'https://github.com/chrisallenlane/cheat', packages = [ 'cheat', - 'cheat.cheatsheets', 'cheat.test', ], - package_data = { - 'cheat.cheatsheets': [f for f in os.listdir('cheat/cheatsheets') if '.' not in f] - }, scripts = ['bin/cheat'], install_requires = [ 'docopt >= 0.6.1', 'pygments >= 1.6.0', - ] + ], + data_files = [ + ('/usr/share/cheat', cheat_files), + ], ) From a651426075c3f5fd39dfa9ef35d8f96babfda33e Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Thu, 24 May 2018 14:12:43 +0200 Subject: [PATCH 02/10] Add reading settings from configuration file --- cheat/__init__.py | 1 + cheat/configuration.py | 76 ++++++++++++++++++++++++++++++++++++++++++ cheat/sheets.py | 8 ++--- cheat/utils.py | 13 ++++---- config/cheat | 1 + setup.py | 2 +- 6 files changed, 89 insertions(+), 12 deletions(-) create mode 100644 cheat/configuration.py create mode 100644 config/cheat diff --git a/cheat/__init__.py b/cheat/__init__.py index 04d9a4c..2560cf4 100644 --- a/cheat/__init__.py +++ b/cheat/__init__.py @@ -1,3 +1,4 @@ from . import sheet from . import sheets from . import utils +from . import configuration \ No newline at end of file diff --git a/cheat/configuration.py b/cheat/configuration.py new file mode 100644 index 0000000..38e80e2 --- /dev/null +++ b/cheat/configuration.py @@ -0,0 +1,76 @@ +import os +from cheat.utils import warn +import json + +class Configuration: + + def __init__(self): + self._saved_configuration = self._get_configuration() + + + def _get_configuration(self): + # get options from config files and environment vairables + merged_config = {} + + try: + merged_config.update(self._read_configuration_file('/etc/cheat')) + except Exception: + warn('error while parsing global configuration') + + try: + merged_config.update(self._read_configuration_file(os.path.expanduser(os.path.join('~','.config','cheat','cheat')))) + except Exception: + warn('error while parsing user configuration') + + merged_config.update(self._read_env_vars_config()) + + return merged_config + + + def _read_configuration_file(self,path): + # Reads configuration file and returns list of set variables + read_config = {} + if (os.path.isfile(path)): + with open(path) as config_file: + read_config.update(json.load(config_file)) + return read_config + + + def _read_env_vars_config(self): + read_config = {} + + # All these variables are left here because of backwards compatibility + + if (os.environ.get('CHEAT_EDITOR')): + read_config['EDITOR'] = os.environ.get('CHEAT_EDITOR') + + if (os.environ.get('VISUAL')): + read_config['EDITOR'] = os.environ.get('VISUAL') + + keys = ['DEFAULT_CHEAT_DIR','CHEATPATH','CHEATCOLORS','EDITOR'] + + for k in keys: + self._read_env_var(read_config,k) + + return read_config + + + def _read_env_var(self,current_config,key): + if (os.environ.get(key)): + current_config[key] = os.environ.get(key) + + + def get_default_cheat_dir(self): + return self._saved_configuration.get('DEFAULT_CHEAT_DIR') + + + def get_cheatpath(self): + return self._saved_configuration.get('CHEATPATH') + + + def get_cheatcolors(self): + return self._saved_configuration.get('CHEATCOLORS') + + + def get_editor(self): + return self._saved_configuration.get('EDITOR') \ No newline at end of file diff --git a/cheat/sheets.py b/cheat/sheets.py index 7a44342..c106786 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -1,14 +1,14 @@ import os -from cheat import cheatsheets from cheat.utils import die from cheat.utils import highlight +from cheat.configuration import Configuration def default_path(): """ Returns the default cheatsheet path """ # determine the default cheatsheet dir - default_sheets_dir = os.environ.get('DEFAULT_CHEAT_DIR') or os.path.join('~', '.cheat') + default_sheets_dir = Configuration().get_default_cheat_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 @@ -57,8 +57,8 @@ def paths(): ] # merge the CHEATPATH paths into the sheet_paths - if 'CHEATPATH' in os.environ and os.environ['CHEATPATH']: - for path in os.environ['CHEATPATH'].split(os.pathsep): + if Configuration().get_cheatpath(): + for path in Configuration().get_cheatpath().split(os.pathsep): if os.path.isdir(path): sheet_paths.append(path) diff --git a/cheat/utils.py b/cheat/utils.py index aa12dcd..c4202ec 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -3,6 +3,8 @@ import os import subprocess import sys +from cheat.configuration import Configuration + def highlight(needle, haystack): """ Highlights a search term matched within a line """ @@ -26,7 +28,7 @@ def colorize(sheet_content): """ Colorizes cheatsheet content if so configured """ # only colorize if configured to do so, and if stdout is a tty - if os.environ.get('CHEATCOLORS') != 'true' or not sys.stdout.isatty(): + if not Configuration().get_cheatcolors() or not sys.stdout.isatty(): return sheet_content # don't attempt to colorize an empty cheatsheet @@ -68,16 +70,13 @@ def editor(): """ Determines the user's preferred editor """ # determine which editor to use - editor = os.environ.get('CHEAT_EDITOR') \ - or os.environ.get('VISUAL') \ - or os.environ.get('EDITOR') \ - or False + editor = Configuration().get_editor() # assert that the editor is set - if editor == False: + if (not editor): die( 'You must set a CHEAT_EDITOR, VISUAL, or EDITOR environment ' - 'variable in order to create/edit a cheatsheet.' + 'variable or setting in order to create/edit a cheatsheet.' ) return editor diff --git a/config/cheat b/config/cheat new file mode 100644 index 0000000..3a7537b --- /dev/null +++ b/config/cheat @@ -0,0 +1 @@ +{"CHEATCOLORS":true,"EDITOR":"vi"} diff --git a/setup.py b/setup.py index 0bcfa1f..8a3da9d 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ from distutils.core import setup -import glob import os cheat_files = [] @@ -28,5 +27,6 @@ setup( ], data_files = [ ('/usr/share/cheat', cheat_files), + ('/etc', ['config/cheat']), ], ) From 7814de96d2c49c15c55e3c74d8f30a6665dbe2e5 Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Wed, 30 May 2018 08:49:52 +0200 Subject: [PATCH 03/10] Add classes for better readability --- bin/cheat | 16 +++-- cheat/configuration.py | 6 +- cheat/sheet.py | 121 ++++++++++++++++--------------- cheat/sheets.py | 158 +++++++++++++++++++++-------------------- cheat/utils.py | 155 +++++++++++++++++++++------------------- 5 files changed, 241 insertions(+), 215 deletions(-) diff --git a/bin/cheat b/bin/cheat index 40ab7e5..22b5b28 100755 --- a/bin/cheat +++ b/bin/cheat @@ -36,9 +36,10 @@ Examples: # require the dependencies from __future__ import print_function -from cheat import sheets, sheet -from cheat.utils import colorize -from cheat.utils import die +from cheat.sheets import Sheets +from cheat.sheet import Sheet +from cheat.utils import Utils +from cheat.configuration import Configuration from docopt import docopt import os @@ -59,6 +60,11 @@ if __name__ == '__main__': # parse the command-line options options = docopt(__doc__, version='cheat 2.3.1') + config = Configuration() + sheets = Sheets(config.get_default_cheat_dir(),config.get_cheatpath()) + sheet = Sheet(config.get_default_cheat_dir(),config.get_cheatpath(),config.get_editor()) + utils = Utils(config.get_cheatcolors(),config.get_editor()) + # list directories if options['--directories']: print("\n".join(sheets.paths())) @@ -73,8 +79,8 @@ if __name__ == '__main__': # search among the cheatsheets elif options['--search']: - print(colorize(sheets.search(options[''])), end="") + print(utils.colorize(sheets.search(options[''])), end="") # print the cheatsheet else: - print(colorize(sheet.read(options[''])), end="") + print(utils.colorize(sheet.read(options[''])), end="") diff --git a/cheat/configuration.py b/cheat/configuration.py index 38e80e2..e89b849 100644 --- a/cheat/configuration.py +++ b/cheat/configuration.py @@ -1,5 +1,5 @@ import os -from cheat.utils import warn +from cheat.utils import Utils import json class Configuration: @@ -15,12 +15,12 @@ class Configuration: try: merged_config.update(self._read_configuration_file('/etc/cheat')) except Exception: - warn('error while parsing global configuration') + Utils.warn('error while parsing global configuration') try: merged_config.update(self._read_configuration_file(os.path.expanduser(os.path.join('~','.config','cheat','cheat')))) except Exception: - warn('error while parsing user configuration') + Utils.warn('error while parsing user configuration') merged_config.update(self._read_env_vars_config()) diff --git a/cheat/sheet.py b/cheat/sheet.py index ff1ce27..c9a0bb0 100644 --- a/cheat/sheet.py +++ b/cheat/sheet.py @@ -1,76 +1,85 @@ import os import shutil -from cheat import sheets -from cheat.utils import die, open_with_editor - -def copy(current_sheet_path, new_sheet_path): - """ Copies a sheet to a new path """ - - # attempt to copy the sheet to DEFAULT_CHEAT_DIR - try: - shutil.copy(current_sheet_path, new_sheet_path) - - # fail gracefully if the cheatsheet cannot be copied. This can happen if - # DEFAULT_CHEAT_DIR does not exist - except IOError: - die('Could not copy cheatsheet for editing.') +from cheat.sheets import Sheets +from cheat.utils import Utils -def create_or_edit(sheet): - """ Creates or edits a cheatsheet """ - - # if the cheatsheet does not exist - if not exists(sheet): - create(sheet) - - # if the cheatsheet exists but not in the default_path, copy it to the - # default path before editing - elif exists(sheet) and not exists_in_default_path(sheet): - copy(path(sheet), os.path.join(sheets.default_path(), sheet)) - edit(sheet) - - # if it exists and is in the default path, then just open it - else: - edit(sheet) +class Sheet: -def create(sheet): - """ Creates a cheatsheet """ - new_sheet_path = os.path.join(sheets.default_path(), sheet) - open_with_editor(new_sheet_path) + def __init__(self,default_cheat_dir,cheatpath,editor_exec): + self.sheets_instance = Sheets(default_cheat_dir,cheatpath) + self.utils_instance = Utils(None,editor_exec) -def edit(sheet): - """ Opens a cheatsheet for editing """ - open_with_editor(path(sheet)) + def copy(self,current_sheet_path, new_sheet_path): + """ Copies a sheet to a new path """ + + # attempt to copy the sheet to DEFAULT_CHEAT_DIR + try: + shutil.copy(current_sheet_path, new_sheet_path) + + # fail gracefully if the cheatsheet cannot be copied. This can happen if + # DEFAULT_CHEAT_DIR does not exist + except IOError: + Utils.die('Could not copy cheatsheet for editing.') -def exists(sheet): - """ Predicate that returns true if the sheet exists """ - return sheet in sheets.get() and os.access(path(sheet), os.R_OK) + def create_or_edit(self,sheet): + """ Creates or edits a cheatsheet """ + + # if the cheatsheet does not exist + if not self.exists(sheet): + self.create(sheet) + + # if the cheatsheet exists but not in the default_path, copy it to the + # default path before editing + elif self.exists(sheet) and not self.exists_in_default_path(sheet): + self.copy(self.path(sheet), os.path.join(self.sheets_instance.default_path(), sheet)) + self.edit(sheet) + + # if it exists and is in the default path, then just open it + else: + self.edit(sheet) -def exists_in_default_path(sheet): - """ Predicate that returns true if the sheet exists in default_path""" - default_path_sheet = os.path.join(sheets.default_path(), sheet) - return sheet in sheets.get() and os.access(default_path_sheet, os.R_OK) + def create(self,sheet): + """ Creates a cheatsheet """ + new_sheet_path = os.path.join(self.sheets_instance.default_path(), sheet) + self.utils_instance.open_with_editor(new_sheet_path) -def is_writable(sheet): - """ Predicate that returns true if the sheet is writeable """ - return sheet in sheets.get() and os.access(path(sheet), os.W_OK) + def edit(self,sheet): + """ Opens a cheatsheet for editing """ + self.utils_instance.open_with_editor(self.path(sheet)) -def path(sheet): - """ Returns a sheet's filesystem path """ - return sheets.get()[sheet] + def exists(self,sheet): + """ Predicate that returns true if the sheet exists """ + return sheet in self.sheets_instance.get() and os.access(self.path(sheet), os.R_OK) -def read(sheet): - """ Returns the contents of the cheatsheet as a String """ - if not exists(sheet): - die('No cheatsheet found for ' + sheet) + def exists_in_default_path(self,sheet): + """ Predicate that returns true if the sheet exists in default_path""" + default_path_sheet = os.path.join(self.sheets_instance.default_path(), sheet) + return sheet in self.sheets_instance.get() and os.access(default_path_sheet, os.R_OK) - with open(path(sheet)) as cheatfile: - return cheatfile.read() + + def is_writable(self,sheet): + """ Predicate that returns true if the sheet is writeable """ + return sheet in self.sheets_instance.get() and os.access(self.path(sheet), os.W_OK) + + + def path(self,sheet): + """ Returns a sheet's filesystem path """ + return self.sheets_instance.get()[sheet] + + + def read(self,sheet): + """ Returns the contents of the cheatsheet as a String """ + if not self.exists(sheet): + Utils.die('No cheatsheet found for ' + sheet) + + with open(self.path(sheet)) as cheatfile: + return cheatfile.read() diff --git a/cheat/sheets.py b/cheat/sheets.py index c106786..107bad3 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -1,94 +1,100 @@ import os -from cheat.utils import die -from cheat.utils import highlight from cheat.configuration import Configuration +from cheat.utils import Utils -def default_path(): - """ Returns the default cheatsheet path """ - - # determine the default cheatsheet dir - default_sheets_dir = Configuration().get_default_cheat_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 - if not os.path.isdir(default_sheets_dir): - try: - # @kludge: unclear on why this is necessary - os.umask(0000) - os.mkdir(default_sheets_dir) - - except OSError: - die('Could not create DEFAULT_CHEAT_DIR') - - # assert that the DEFAULT_CHEAT_DIR is readable and writable - if not os.access(default_sheets_dir, os.R_OK): - die('The DEFAULT_CHEAT_DIR (' + default_sheets_dir +') is not readable.') - if not os.access(default_sheets_dir, os.W_OK): - die('The DEFAULT_CHEAT_DIR (' + default_sheets_dir +') is not writable.') - - # return the default dir - return default_sheets_dir +class Sheets: -def get(): - """ Assembles a dictionary of cheatsheets as name => file-path """ - cheats = {} - - # otherwise, scan the filesystem - for cheat_dir in reversed(paths()): - cheats.update( - dict([ - (cheat, os.path.join(cheat_dir, cheat)) - for cheat in os.listdir(cheat_dir) - if not cheat.startswith('.') - and not cheat.startswith('__') - ]) - ) - - return cheats + def __init__(self,default_cheat_dir,cheatpath): + self.default_cheat_dir = default_cheat_dir + self.cheatpath = cheatpath -def paths(): - """ Assembles a list of directories containing cheatsheets """ - sheet_paths = [ - default_path(), - '/usr/share/cheat', - ] + def default_path(self): + """ Returns the default cheatsheet path """ - # merge the CHEATPATH paths into the sheet_paths - if Configuration().get_cheatpath(): - for path in Configuration().get_cheatpath().split(os.pathsep): - if os.path.isdir(path): - sheet_paths.append(path) + # determine the default cheatsheet dir + default_sheets_dir = self.default_cheat_dir or os.path.join('~', '.cheat') + default_sheets_dir = os.path.expanduser(os.path.expandvars(default_sheets_dir)) - if not sheet_paths: - die('The DEFAULT_CHEAT_DIR dir does not exist or the CHEATPATH is not set.') + # create the DEFAULT_CHEAT_DIR if it does not exist + if not os.path.isdir(default_sheets_dir): + try: + # @kludge: unclear on why this is necessary + os.umask(0000) + os.mkdir(default_sheets_dir) - return sheet_paths + except OSError: + Utils.die('Could not create DEFAULT_CHEAT_DIR') + + # assert that the DEFAULT_CHEAT_DIR is readable and writable + if not os.access(default_sheets_dir, os.R_OK): + Utils.die('The DEFAULT_CHEAT_DIR (' + default_sheets_dir +') is not readable.') + if not os.access(default_sheets_dir, os.W_OK): + Utils.die('The DEFAULT_CHEAT_DIR (' + default_sheets_dir +') is not writable.') + + # return the default dir + return default_sheets_dir -def list(): - """ Lists the available cheatsheets """ - sheet_list = '' - pad_length = max([len(x) for x in get().keys()]) + 4 - for sheet in sorted(get().items()): - sheet_list += sheet[0].ljust(pad_length) + sheet[1] + "\n" - return sheet_list + def get(self): + """ Assembles a dictionary of cheatsheets as name => file-path """ + cheats = {} + + # otherwise, scan the filesystem + for cheat_dir in reversed(self.paths()): + cheats.update( + dict([ + (cheat, os.path.join(cheat_dir, cheat)) + for cheat in os.listdir(cheat_dir) + if not cheat.startswith('.') + and not cheat.startswith('__') + ]) + ) + + return cheats -def search(term): - """ Searches all cheatsheets for the specified term """ - result = '' - lowered_term = term.lower() + def paths(self): + """ Assembles a list of directories containing cheatsheets """ + sheet_paths = [ + self.default_path(), + '/usr/share/cheat', + ] - for cheatsheet in sorted(get().items()): - match = '' - for line in open(cheatsheet[1]): - if term in line: - match += ' ' + highlight(term, line) + # merge the CHEATPATH paths into the sheet_paths + if self.cheatpath: + for path in self.cheatpath.split(os.pathsep): + if os.path.isdir(path): + sheet_paths.append(path) - if match != '': - result += cheatsheet[0] + ":\n" + match + "\n" + if not sheet_paths: + Utils.die('The DEFAULT_CHEAT_DIR dir does not exist or the CHEATPATH is not set.') - return result + return sheet_paths + + + def list(self): + """ Lists the available cheatsheets """ + sheet_list = '' + pad_length = max([len(x) for x in self.get().keys()]) + 4 + for sheet in sorted(self.get().items()): + sheet_list += sheet[0].ljust(pad_length) + sheet[1] + "\n" + return sheet_list + + + def search(self,term): + """ Searches all cheatsheets for the specified term """ + result = '' + + for cheatsheet in sorted(self.get().items()): + match = '' + for line in open(cheatsheet[1]): + if term in line: + match += ' ' + line + + if match != '': + result += cheatsheet[0] + ":\n" + match + "\n" + + return result diff --git a/cheat/utils.py b/cheat/utils.py index c4202ec..17e4ba8 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -3,94 +3,99 @@ import os import subprocess import sys -from cheat.configuration import Configuration - -def highlight(needle, haystack): - """ Highlights a search term matched within a line """ - - # if a highlight color is not configured, exit early - if not 'CHEAT_HIGHLIGHT' in os.environ: - return haystack - - # otherwise, attempt to import the termcolor library - try: - from termcolor import colored - - # if the import fails, return uncolored text - except ImportError: - return haystack - - # if the import succeeds, colorize the needle in haystack - return haystack.replace(needle, colored(needle, os.environ.get('CHEAT_HIGHLIGHT'))); +class Utils: -def colorize(sheet_content): - """ Colorizes cheatsheet content if so configured """ + def __init__(self,cheatcolors,editor_executable): + self.displaycolors = cheatcolors + self.editor_executable = editor_executable - # only colorize if configured to do so, and if stdout is a tty - if not Configuration().get_cheatcolors() or not sys.stdout.isatty(): - return sheet_content - # don't attempt to colorize an empty cheatsheet - if not sheet_content.strip(): - return "" + def highlight(self, needle, haystack): + """ Highlights a search term matched within a line """ - # otherwise, attempt to import the pygments library - try: - from pygments import highlight - from pygments.lexers import get_lexer_by_name - from pygments.formatters import TerminalFormatter + # if a highlight color is not configured, exit early + if not 'CHEAT_HIGHLIGHT' in os.environ: + return haystack - # if the import fails, return uncolored text - except ImportError: - return sheet_content - - # otherwise, attempt to colorize - first_line = sheet_content.splitlines()[0] - lexer = get_lexer_by_name('bash') - - # apply syntax-highlighting if the first line is a code-fence - if first_line.startswith('```'): - sheet_content = '\n'.join(sheet_content.split('\n')[1:-2]) + # otherwise, attempt to import the termcolor library try: - lexer = get_lexer_by_name(first_line[3:]) - except Exception: - pass + from termcolor import colored - return highlight(sheet_content, lexer, TerminalFormatter()) + # if the import fails, return uncolored text + except ImportError: + return haystack + + # if the import succeeds, colorize the needle in haystack + return haystack.replace(needle, colored(needle, os.environ.get('CHEAT_HIGHLIGHT'))) -def die(message): - """ Prints a message to stderr and then terminates """ - warn(message) - exit(1) + def colorize(self,sheet_content): + """ Colorizes cheatsheet content if so configured """ + + # only colorize if configured to do so, and if stdout is a tty + if not self.displaycolors or not sys.stdout.isatty(): + return sheet_content + + # don't attempt to colorize an empty cheatsheet + if not sheet_content.strip(): + return "" + + # otherwise, attempt to import the pygments library + try: + from pygments import highlight + from pygments.lexers import get_lexer_by_name + from pygments.formatters import TerminalFormatter + + # if the import fails, return uncolored text + except ImportError: + return sheet_content + + # otherwise, attempt to colorize + first_line = sheet_content.splitlines()[0] + lexer = get_lexer_by_name('bash') + + # apply syntax-highlighting if the first line is a code-fence + if first_line.startswith('```'): + sheet_content = '\n'.join(sheet_content.split('\n')[1:-2]) + try: + lexer = get_lexer_by_name(first_line[3:]) + except Exception: + pass + + return highlight(sheet_content, lexer, TerminalFormatter()) -def editor(): - """ Determines the user's preferred editor """ - - # determine which editor to use - editor = Configuration().get_editor() - - # assert that the editor is set - if (not editor): - die( - 'You must set a CHEAT_EDITOR, VISUAL, or EDITOR environment ' - 'variable or setting in order to create/edit a cheatsheet.' - ) - - return editor + @staticmethod + def die(message): + """ Prints a message to stderr and then terminates """ + Utils.warn(message) + exit(1) -def open_with_editor(filepath): - """ Open `filepath` using the EDITOR specified by the environment variables """ - editor_cmd = editor().split() - try: - subprocess.call(editor_cmd + [filepath]) - except OSError: - die('Could not launch ' + editor()) + def editor(self): + """ Determines the user's preferred editor """ + + # assert that the editor is set + if (not self.editor_executable): + 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 -def warn(message): - """ Prints a message to stderr """ - print((message), file=sys.stderr) + def open_with_editor(self,filepath): + """ Open `filepath` using the EDITOR specified by the environment variables """ + editor_cmd = self.editor().split() + try: + subprocess.call(editor_cmd + [filepath]) + except OSError: + Utils.die('Could not launch ' + self.editor()) + + + @staticmethod + def warn(message): + """ Prints a message to stderr """ + print((message), file=sys.stderr) From 879a58b7215630f35d977bca3777a4383c2b1159 Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Tue, 21 Aug 2018 15:40:47 +0200 Subject: [PATCH 04/10] Read env vars for global and local config path - allows to change these paths for testing purposes and also gives user option to change his config paths --- cheat/configuration.py | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/cheat/configuration.py b/cheat/configuration.py index e89b849..edbe4d7 100644 --- a/cheat/configuration.py +++ b/cheat/configuration.py @@ -2,9 +2,13 @@ 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() @@ -12,15 +16,15 @@ class Configuration: # get options from config files and environment vairables merged_config = {} - try: - merged_config.update(self._read_configuration_file('/etc/cheat')) - except Exception: - Utils.warn('error while parsing global configuration') + try: + merged_config.update(self._read_configuration_file(self.glob_config_path)) + except Exception as e: + Utils.warn('error while parsing global configuration Reason: ' + e.message) try: - merged_config.update(self._read_configuration_file(os.path.expanduser(os.path.join('~','.config','cheat','cheat')))) - except Exception: - Utils.warn('error while parsing user configuration') + merged_config.update(self._read_configuration_file(self.local_config_path)) + except Exception as e: + Utils.warn('error while parsing user configuration Reason: ' + e.message) merged_config.update(self._read_env_vars_config()) @@ -51,7 +55,7 @@ class Configuration: for k in keys: self._read_env_var(read_config,k) - + return read_config @@ -60,6 +64,16 @@ class Configuration: current_config[key] = os.environ.get(key) + 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): + self.local_config_path = os.environ.get('CHEAT_LOCAL_CONF_PATH') \ + or os.path.expanduser('~/.config/cheat/cheat') + + def get_default_cheat_dir(self): return self._saved_configuration.get('DEFAULT_CHEAT_DIR') @@ -70,7 +84,7 @@ class Configuration: def get_cheatcolors(self): return self._saved_configuration.get('CHEATCOLORS') - + def get_editor(self): - return self._saved_configuration.get('EDITOR') \ No newline at end of file + return self._saved_configuration.get('EDITOR') From 3a4c2a887db0d97d7827d4a6fc1271a8b798b822 Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Tue, 21 Aug 2018 15:42:52 +0200 Subject: [PATCH 05/10] Add ConfigurationTestCase - tests prove descending hiearchy of config system - env vars, local config file, global config file --- tests/test_configuration.py | 86 +++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 tests/test_configuration.py diff --git a/tests/test_configuration.py b/tests/test_configuration.py new file mode 100644 index 0000000..187ef16 --- /dev/null +++ b/tests/test_configuration.py @@ -0,0 +1,86 @@ +import unittest2 +import os +import shutil +from cheat.configuration import Configuration + + +def _set_loc_conf(key, value): + _path = os.path.dirname(os.path.abspath(__file__)) + '/home/.config/cheat/cheat' + if value == None: + os.remove(_path) + else: + if not os.path.exists(os.path.dirname(_path)): + os.makedirs(os.path.dirname(_path)) + f = open(_path,"w+") + f.write('{"'+ key +'":"'+ value +'"}') + f.close() + + +def _set_glob_conf(key, value): + _path = os.path.dirname(os.path.abspath(__file__))+ "/etc/cheat" + if value == None: + os.remove(_path) + else: + if not os.path.exists(os.path.dirname(_path)): + os.mkdir(os.path.dirname(_path)) + f = open(_path,"w+") + f.write('{"'+ key +'":"'+ value +'"}' ) + f.close() + + +def _set_env_var(key, value): + if value == None: + del os.environ[key] + else: + os.environ[key] = value + + +def _configuration_key_test(TestConfiguration, key,values, conf_get_method): + for glob_conf in values: + _set_glob_conf(key,glob_conf) + for loc_conf in values: + _set_loc_conf(key,loc_conf) + for env_conf in values: + _set_env_var(key,env_conf) + if env_conf: + TestConfiguration.assertEqual(conf_get_method(Configuration()),env_conf) + elif loc_conf: + TestConfiguration.assertEqual(conf_get_method(Configuration()),loc_conf) + elif glob_conf: + TestConfiguration.assertEqual(conf_get_method(Configuration()),glob_conf) + else: + TestConfiguration.assertEqual(conf_get_method(Configuration()),None) + + +class ConfigurationTestCase(unittest2.TestCase): + + + def setUp(self): + os.environ['CHEAT_GLOBAL_CONF_PATH'] = os.path.dirname(os.path.abspath(__file__)) \ + + '/etc/cheat' + os.environ['CHEAT_LOCAL_CONF_PATH'] = os.path.dirname(os.path.abspath(__file__)) \ + + '/home/.config/cheat/cheat' + + + def test_get_editor(self): + _configuration_key_test(self,"EDITOR",["nano","vim","gedit",None], + Configuration.get_editor) + + + def test_get_cheatcolors(self): + _configuration_key_test(self,"CHEATCOLORS",["true",None], + Configuration.get_cheatcolors) + + + def test_get_cheatpath(self): + _configuration_key_test(self,"CHEATPATH",["/etc/myglobalcheats", + "/etc/anotherglobalcheats","/rootcheats",None],Configuration.get_cheatpath) + + + def test_get_defaultcheatdir(self): + _configuration_key_test(self,"DEFAULT_CHEAT_DIR",["/etc/myglobalcheats", + "/etc/anotherglobalcheats","/rootcheats",None],Configuration.get_default_cheat_dir) + + def tearDown(self): + shutil.rmtree(os.path.dirname(os.path.abspath(__file__)) +'/etc') + shutil.rmtree(os.path.dirname(os.path.abspath(__file__)) +'/home') From 5eec6bf040a205b4d175316a1347c74967da4558 Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Tue, 15 Jan 2019 19:13:30 +0100 Subject: [PATCH 06/10] Improve handling of settings Constructors of classes which need direct access to configuration now take Config class instance as parameter which will give them better maintainability in the future CHEAT_HIGHLIGHT has been added to Configuration class --- bin/cheat | 6 +++--- cheat/configuration.py | 12 +++++++++++- cheat/sheet.py | 25 ++++++++++++------------- cheat/sheets.py | 17 ++++++++--------- cheat/utils.py | 18 +++++++++--------- 5 files changed, 43 insertions(+), 35 deletions(-) diff --git a/bin/cheat b/bin/cheat index 22b5b28..2c15439 100755 --- a/bin/cheat +++ b/bin/cheat @@ -61,9 +61,9 @@ if __name__ == '__main__': options = docopt(__doc__, version='cheat 2.3.1') config = Configuration() - sheets = Sheets(config.get_default_cheat_dir(),config.get_cheatpath()) - sheet = Sheet(config.get_default_cheat_dir(),config.get_cheatpath(),config.get_editor()) - utils = Utils(config.get_cheatcolors(),config.get_editor()) + sheets = Sheets(config) + utils = Utils(config) + sheet = Sheet(sheets, utils) # list directories if options['--directories']: diff --git a/cheat/configuration.py b/cheat/configuration.py index edbe4d7..5062b82 100644 --- a/cheat/configuration.py +++ b/cheat/configuration.py @@ -28,6 +28,8 @@ class Configuration: merged_config.update(self._read_env_vars_config()) + + return merged_config @@ -51,7 +53,12 @@ class Configuration: if (os.environ.get('VISUAL')): read_config['EDITOR'] = os.environ.get('VISUAL') - keys = ['DEFAULT_CHEAT_DIR','CHEATPATH','CHEATCOLORS','EDITOR'] + keys = ['DEFAULT_CHEAT_DIR', + 'CHEATPATH', + 'CHEATCOLORS', + 'EDITOR', + 'CHEAT_HIGHLIGHT' + ] for k in keys: self._read_env_var(read_config,k) @@ -88,3 +95,6 @@ class Configuration: def get_editor(self): return self._saved_configuration.get('EDITOR') + + def get_highlight(self): + return self._saved_configuration.get('CHEAT_HIGHLIGHT') diff --git a/cheat/sheet.py b/cheat/sheet.py index c9a0bb0..df77596 100644 --- a/cheat/sheet.py +++ b/cheat/sheet.py @@ -1,16 +1,15 @@ import os import shutil -from cheat.sheets import Sheets from cheat.utils import Utils class Sheet: - def __init__(self,default_cheat_dir,cheatpath,editor_exec): - self.sheets_instance = Sheets(default_cheat_dir,cheatpath) - self.utils_instance = Utils(None,editor_exec) + def __init__(self, sheets, utils): + self._sheets = sheets + self._utils = utils def copy(self,current_sheet_path, new_sheet_path): @@ -36,7 +35,7 @@ class Sheet: # if the cheatsheet exists but not in the default_path, copy it to the # default path before editing elif self.exists(sheet) and not self.exists_in_default_path(sheet): - self.copy(self.path(sheet), os.path.join(self.sheets_instance.default_path(), sheet)) + self.copy(self.path(sheet), os.path.join(self._sheets.default_path(), sheet)) self.edit(sheet) # if it exists and is in the default path, then just open it @@ -46,34 +45,34 @@ class Sheet: def create(self,sheet): """ Creates a cheatsheet """ - new_sheet_path = os.path.join(self.sheets_instance.default_path(), sheet) - self.utils_instance.open_with_editor(new_sheet_path) + new_sheet_path = os.path.join(self._sheets.default_path(), sheet) + self._utils.open_with_editor(new_sheet_path) def edit(self,sheet): """ Opens a cheatsheet for editing """ - self.utils_instance.open_with_editor(self.path(sheet)) + self._utils.open_with_editor(self.path(sheet)) def exists(self,sheet): """ Predicate that returns true if the sheet exists """ - return sheet in self.sheets_instance.get() and os.access(self.path(sheet), os.R_OK) + return sheet in self._sheets.get() and os.access(self.path(sheet), os.R_OK) def exists_in_default_path(self,sheet): """ Predicate that returns true if the sheet exists in default_path""" - default_path_sheet = os.path.join(self.sheets_instance.default_path(), sheet) - return sheet in self.sheets_instance.get() and os.access(default_path_sheet, os.R_OK) + default_path_sheet = os.path.join(self._sheets.default_path(), sheet) + return sheet in self._sheets.get() and os.access(default_path_sheet, os.R_OK) def is_writable(self,sheet): """ Predicate that returns true if the sheet is writeable """ - return sheet in self.sheets_instance.get() and os.access(self.path(sheet), os.W_OK) + return sheet in self._sheets.get() and os.access(self.path(sheet), os.W_OK) def path(self,sheet): """ Returns a sheet's filesystem path """ - return self.sheets_instance.get()[sheet] + return self._sheets.get()[sheet] def read(self,sheet): diff --git a/cheat/sheets.py b/cheat/sheets.py index 107bad3..49c4b10 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -1,21 +1,20 @@ import os -from cheat.configuration import Configuration from cheat.utils import Utils class Sheets: - def __init__(self,default_cheat_dir,cheatpath): - self.default_cheat_dir = default_cheat_dir - self.cheatpath = cheatpath - + def __init__(self, config): + self._default_cheat_dir = config.get_default_cheat_dir() + self._cheatpath = config.get_cheatpath() + 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 os.path.join('~', '.cheat') + default_sheets_dir = self._default_cheat_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 @@ -64,8 +63,8 @@ class Sheets: ] # merge the CHEATPATH paths into the sheet_paths - if self.cheatpath: - for path in self.cheatpath.split(os.pathsep): + if self._cheatpath: + for path in self._cheatpath.split(os.pathsep): if os.path.isdir(path): sheet_paths.append(path) @@ -92,7 +91,7 @@ class Sheets: match = '' for line in open(cheatsheet[1]): if term in line: - match += ' ' + line + match += ' ' + self._utils.highlight(term, line) if match != '': result += cheatsheet[0] + ":\n" + match + "\n" diff --git a/cheat/utils.py b/cheat/utils.py index 17e4ba8..870c570 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -6,16 +6,16 @@ import sys class Utils: - def __init__(self,cheatcolors,editor_executable): - self.displaycolors = cheatcolors - self.editor_executable = editor_executable - + def __init__(self,config): + self._displaycolors = config.get_cheatcolors() + self._editor_executable = config.get_editor() + self._highlight_color = config.get_highlight() def highlight(self, needle, haystack): """ Highlights a search term matched within a line """ # if a highlight color is not configured, exit early - if not 'CHEAT_HIGHLIGHT' in os.environ: + if not self._highlight_color: return haystack # otherwise, attempt to import the termcolor library @@ -27,14 +27,14 @@ class Utils: return haystack # if the import succeeds, colorize the needle in haystack - return haystack.replace(needle, colored(needle, os.environ.get('CHEAT_HIGHLIGHT'))) + return haystack.replace(needle, colored(needle, self._highlight_color)) def colorize(self,sheet_content): """ Colorizes cheatsheet content if so configured """ # only colorize if configured to do so, and if stdout is a tty - if not self.displaycolors or not sys.stdout.isatty(): + if not self._displaycolors or not sys.stdout.isatty(): return sheet_content # don't attempt to colorize an empty cheatsheet @@ -77,13 +77,13 @@ class Utils: """ Determines the user's preferred editor """ # assert that the editor is set - if (not self.editor_executable): + if (not self._editor_executable): 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._editor_executable def open_with_editor(self,filepath): From a2e028fd1923a19e5aa6e7031790bb04df431843 Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Tue, 15 Jan 2019 19:18:15 +0100 Subject: [PATCH 07/10] Move validation of CHEAT_HIGHLIGHT value to Configuration class Method _check_configuration should be used for validating all bad values from now on --- bin/cheat | 10 ---------- cheat/configuration.py | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/bin/cheat b/bin/cheat index 2c15439..a95d592 100755 --- a/bin/cheat +++ b/bin/cheat @@ -46,16 +46,6 @@ import os if __name__ == '__main__': - # validate CHEAT_HIGHLIGHT values if set - colors = [ - 'grey' , 'red' , 'green' , 'yellow' , - 'blue' , 'magenta' , 'cyan' , 'white' , - ] - if ( - os.environ.get('CHEAT_HIGHLIGHT') and - os.environ.get('CHEAT_HIGHLIGHT') not in colors - ): - die("%s %s" %('CHEAT_HIGHLIGHT must be one of:', colors)) # parse the command-line options options = docopt(__doc__, version='cheat 2.3.1') diff --git a/cheat/configuration.py b/cheat/configuration.py index 5062b82..cf3e14e 100644 --- a/cheat/configuration.py +++ b/cheat/configuration.py @@ -28,7 +28,7 @@ class Configuration: merged_config.update(self._read_env_vars_config()) - + self._check_configuration(merged_config) return merged_config @@ -65,6 +65,19 @@ class Configuration: return read_config + def _check_configuration(self, config): + """ Check values in config and warn user or die """ + + # validate CHEAT_HIGHLIGHT values if set + colors = [ + 'grey' , 'red' , 'green' , 'yellow' , + 'blue' , 'magenta' , 'cyan' , 'white' , + ] + if ( + config.get('CHEAT_HIGHLIGHT') and + config.get('CHEAT_HIGHLIGHT') not in colors + ): + Utils.die("%s %s" %('CHEAT_HIGHLIGHT must be one of:', colors)) def _read_env_var(self,current_config,key): if (os.environ.get(key)): From 4d19505b7941b06f73e1c490e4bea9541f3795ba Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Tue, 15 Jan 2019 19:38:24 +0100 Subject: [PATCH 08/10] Conform code to pep8 --- bin/cheat | 1 - cheat/configuration.py | 49 +++++++++++++++++++++--------------------- cheat/sheet.py | 44 ++++++++++++++++--------------------- cheat/sheets.py | 25 +++++++++++---------- cheat/utils.py | 17 ++++++--------- 5 files changed, 63 insertions(+), 73 deletions(-) diff --git a/bin/cheat b/bin/cheat index a95d592..49e9c03 100755 --- a/bin/cheat +++ b/bin/cheat @@ -46,7 +46,6 @@ import os if __name__ == '__main__': - # parse the command-line options options = docopt(__doc__, version='cheat 2.3.1') diff --git a/cheat/configuration.py b/cheat/configuration.py index cf3e14e..e0e95da 100644 --- a/cheat/configuration.py +++ b/cheat/configuration.py @@ -5,26 +5,32 @@ 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 = {} try: - merged_config.update(self._read_configuration_file(self.glob_config_path)) + merged_config.update( + self._read_configuration_file(self.glob_config_path) + ) except Exception as e: - Utils.warn('error while parsing global configuration Reason: ' + e.message) + Utils.warn('error while parsing global configuration Reason: ' + + e.message + ) try: - merged_config.update(self._read_configuration_file(self.local_config_path)) + merged_config.update( + self._read_configuration_file(self.local_config_path) + ) except Exception as e: - Utils.warn('error while parsing user configuration Reason: ' + e.message) + Utils.warn('error while parsing user configuration Reason: ' + + e.message + ) merged_config.update(self._read_env_vars_config()) @@ -32,8 +38,7 @@ class Configuration: return merged_config - - def _read_configuration_file(self,path): + def _read_configuration_file(self, path): # Reads configuration file and returns list of set variables read_config = {} if (os.path.isfile(path)): @@ -41,7 +46,6 @@ class Configuration: read_config.update(json.load(config_file)) return read_config - def _read_env_vars_config(self): read_config = {} @@ -58,10 +62,10 @@ class Configuration: 'CHEATCOLORS', 'EDITOR', 'CHEAT_HIGHLIGHT' - ] + ] for k in keys: - self._read_env_var(read_config,k) + self._read_env_var(read_config, k) return read_config @@ -70,42 +74,37 @@ class Configuration: # validate CHEAT_HIGHLIGHT values if set colors = [ - 'grey' , 'red' , 'green' , 'yellow' , - 'blue' , 'magenta' , 'cyan' , 'white' , + 'grey', 'red', 'green', 'yellow', + 'blue', 'magenta', 'cyan', 'white' ] if ( config.get('CHEAT_HIGHLIGHT') and config.get('CHEAT_HIGHLIGHT') not in colors ): - Utils.die("%s %s" %('CHEAT_HIGHLIGHT must be one of:', colors)) + Utils.die("%s %s" % ('CHEAT_HIGHLIGHT must be one of:', colors)) - def _read_env_var(self,current_config,key): + def _read_env_var(self, current_config, key): if (os.environ.get(key)): current_config[key] = os.environ.get(key) - def _get_global_conf_file_path(self): - self.glob_config_path = os.environ.get('CHEAT_GLOBAL_CONF_PATH') \ - or '/etc/cheat' - + self.glob_config_path = (os.environ.get('CHEAT_GLOBAL_CONF_PATH') + or '/etc/cheat') def _get_local_conf_file_path(self): - self.local_config_path = os.environ.get('CHEAT_LOCAL_CONF_PATH') \ - or os.path.expanduser('~/.config/cheat/cheat') - + path = (os.environ.get('CHEAT_LOCAL_CONF_PATH') + or os.path.expanduser('~/.config/cheat/cheat')) + self.local_config_path = path def get_default_cheat_dir(self): return self._saved_configuration.get('DEFAULT_CHEAT_DIR') - def get_cheatpath(self): return self._saved_configuration.get('CHEATPATH') - def get_cheatcolors(self): return self._saved_configuration.get('CHEATCOLORS') - def get_editor(self): return self._saved_configuration.get('EDITOR') diff --git a/cheat/sheet.py b/cheat/sheet.py index df77596..5b27f21 100644 --- a/cheat/sheet.py +++ b/cheat/sheet.py @@ -6,26 +6,23 @@ from cheat.utils import Utils class Sheet: - def __init__(self, sheets, utils): self._sheets = sheets self._utils = utils - - def copy(self,current_sheet_path, new_sheet_path): + def copy(self, current_sheet_path, new_sheet_path): """ Copies a sheet to a new path """ # attempt to copy the sheet to DEFAULT_CHEAT_DIR try: shutil.copy(current_sheet_path, new_sheet_path) - # fail gracefully if the cheatsheet cannot be copied. This can happen if - # DEFAULT_CHEAT_DIR does not exist + # fail gracefully if the cheatsheet cannot be copied. This can happen + # if DEFAULT_CHEAT_DIR does not exist except IOError: Utils.die('Could not copy cheatsheet for editing.') - - def create_or_edit(self,sheet): + def create_or_edit(self, sheet): """ Creates or edits a cheatsheet """ # if the cheatsheet does not exist @@ -35,47 +32,44 @@ class Sheet: # if the cheatsheet exists but not in the default_path, copy it to the # default path before editing elif self.exists(sheet) and not self.exists_in_default_path(sheet): - self.copy(self.path(sheet), os.path.join(self._sheets.default_path(), sheet)) + self.copy(self.path(sheet), + os.path.join(self._sheets.default_path(), sheet)) self.edit(sheet) # if it exists and is in the default path, then just open it else: self.edit(sheet) - - def create(self,sheet): + def create(self, sheet): """ Creates a cheatsheet """ new_sheet_path = os.path.join(self._sheets.default_path(), sheet) self._utils.open_with_editor(new_sheet_path) - - def edit(self,sheet): + def edit(self, sheet): """ Opens a cheatsheet for editing """ self._utils.open_with_editor(self.path(sheet)) - - def exists(self,sheet): + def exists(self, sheet): """ Predicate that returns true if the sheet exists """ - return sheet in self._sheets.get() and os.access(self.path(sheet), os.R_OK) + return (sheet in self._sheets.get() and + os.access(self.path(sheet), os.R_OK)) - - def exists_in_default_path(self,sheet): + def exists_in_default_path(self, sheet): """ Predicate that returns true if the sheet exists in default_path""" default_path_sheet = os.path.join(self._sheets.default_path(), sheet) - return sheet in self._sheets.get() and os.access(default_path_sheet, os.R_OK) + return (sheet in self._sheets.get() and + os.access(default_path_sheet, os.R_OK)) - - def is_writable(self,sheet): + def is_writable(self, sheet): """ Predicate that returns true if the sheet is writeable """ - return sheet in self._sheets.get() and os.access(self.path(sheet), os.W_OK) + return (sheet in self._sheets.get() and + os.access(self.path(sheet), os.W_OK)) - - def path(self,sheet): + def path(self, sheet): """ Returns a sheet's filesystem path """ return self._sheets.get()[sheet] - - def read(self,sheet): + def read(self, sheet): """ Returns the contents of the cheatsheet as a String """ if not self.exists(sheet): Utils.die('No cheatsheet found for ' + sheet) diff --git a/cheat/sheets.py b/cheat/sheets.py index 49c4b10..6f772d2 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -2,8 +2,8 @@ import os from cheat.utils import Utils -class Sheets: +class Sheets: def __init__(self, config): self._default_cheat_dir = config.get_default_cheat_dir() @@ -14,8 +14,10 @@ class Sheets: """ Returns the default cheatsheet path """ # determine the default cheatsheet dir - default_sheets_dir = self._default_cheat_dir or os.path.join('~', '.cheat') - default_sheets_dir = os.path.expanduser(os.path.expandvars(default_sheets_dir)) + default_sheets_dir = (self._default_cheat_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 if not os.path.isdir(default_sheets_dir): @@ -29,14 +31,17 @@ class Sheets: # assert that the DEFAULT_CHEAT_DIR is readable and writable if not os.access(default_sheets_dir, os.R_OK): - Utils.die('The DEFAULT_CHEAT_DIR (' + default_sheets_dir +') is not readable.') + Utils.die('The DEFAULT_CHEAT_DIR (' + + default_sheets_dir + + ') is not readable.') if not os.access(default_sheets_dir, os.W_OK): - Utils.die('The DEFAULT_CHEAT_DIR (' + default_sheets_dir +') is not writable.') + Utils.die('The DEFAULT_CHEAT_DIR (' + + default_sheets_dir + + ') is not writable.') # return the default dir return default_sheets_dir - def get(self): """ Assembles a dictionary of cheatsheets as name => file-path """ cheats = {} @@ -54,7 +59,6 @@ class Sheets: return cheats - def paths(self): """ Assembles a list of directories containing cheatsheets """ sheet_paths = [ @@ -69,11 +73,11 @@ class Sheets: sheet_paths.append(path) if not sheet_paths: - Utils.die('The DEFAULT_CHEAT_DIR dir does not exist or the CHEATPATH is not set.') + Utils.die('The DEFAULT_CHEAT_DIR dir does not exist ' + + 'or the CHEATPATH is not set.') return sheet_paths - def list(self): """ Lists the available cheatsheets """ sheet_list = '' @@ -82,8 +86,7 @@ class Sheets: sheet_list += sheet[0].ljust(pad_length) + sheet[1] + "\n" return sheet_list - - def search(self,term): + def search(self, term): """ Searches all cheatsheets for the specified term """ result = '' diff --git a/cheat/utils.py b/cheat/utils.py index 870c570..3a5b83a 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -3,10 +3,10 @@ import os import subprocess import sys + class Utils: - - def __init__(self,config): + def __init__(self, config): self._displaycolors = config.get_cheatcolors() self._editor_executable = config.get_editor() self._highlight_color = config.get_highlight() @@ -29,8 +29,7 @@ class Utils: # if the import succeeds, colorize the needle in haystack return haystack.replace(needle, colored(needle, self._highlight_color)) - - def colorize(self,sheet_content): + def colorize(self, sheet_content): """ Colorizes cheatsheet content if so configured """ # only colorize if configured to do so, and if stdout is a tty @@ -53,7 +52,7 @@ class Utils: # otherwise, attempt to colorize first_line = sheet_content.splitlines()[0] - lexer = get_lexer_by_name('bash') + lexer = get_lexer_by_name('bash') # apply syntax-highlighting if the first line is a code-fence if first_line.startswith('```'): @@ -65,14 +64,12 @@ class Utils: return highlight(sheet_content, lexer, TerminalFormatter()) - @staticmethod def die(message): """ Prints a message to stderr and then terminates """ Utils.warn(message) exit(1) - def editor(self): """ Determines the user's preferred editor """ @@ -85,16 +82,14 @@ class Utils: return self._editor_executable - - def open_with_editor(self,filepath): - """ Open `filepath` using the EDITOR specified by the environment variables """ + def open_with_editor(self, filepath): + """ Open `filepath` using the EDITOR specified by the env variables """ editor_cmd = self.editor().split() try: subprocess.call(editor_cmd + [filepath]) except OSError: Utils.die('Could not launch ' + self.editor()) - @staticmethod def warn(message): """ Prints a message to stderr """ From 8a8f30679d65fb9e06a7a5446ff0d24b973ea79b Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Thu, 17 Jan 2019 17:10:01 +0100 Subject: [PATCH 09/10] Fix problems with CHEATCOLORS behaviour --- cheat/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cheat/utils.py b/cheat/utils.py index 3a5b83a..59d062e 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -33,7 +33,8 @@ class Utils: """ Colorizes cheatsheet content if so configured """ # only colorize if configured to do so, and if stdout is a tty - if not self._displaycolors or not sys.stdout.isatty(): + if (self._displaycolors not in ["True", "true", "1", 1] or + not sys.stdout.isatty()): return sheet_content # don't attempt to colorize an empty cheatsheet From 80b8cfc06b11234ddf40a9052e5f1869ada2a58c Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Thu, 17 Jan 2019 18:36:02 +0100 Subject: [PATCH 10/10] Add new env variables but hold compatibility with old ones Legacy environmental variables like CHEATCOLORS are now higher in configuration hiearchy than new environmental variables in configuration files --- cheat/configuration.py | 58 +++++++++++++++++++++++++++++------------- cheat/utils.py | 5 +++- config/cheat | 2 +- 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/cheat/configuration.py b/cheat/configuration.py index e0e95da..1700624 100644 --- a/cheat/configuration.py +++ b/cheat/configuration.py @@ -49,23 +49,26 @@ class Configuration: def _read_env_vars_config(self): read_config = {} - # All these variables are left here because of backwards compatibility - - if (os.environ.get('CHEAT_EDITOR')): - read_config['EDITOR'] = os.environ.get('CHEAT_EDITOR') + # NOTE: These variables are left here because of backwards + # compatibility and are supported only as env vars but not in + # configuration file if (os.environ.get('VISUAL')): read_config['EDITOR'] = os.environ.get('VISUAL') - keys = ['DEFAULT_CHEAT_DIR', - 'CHEATPATH', - 'CHEATCOLORS', - 'EDITOR', - 'CHEAT_HIGHLIGHT' - ] + # 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 in keys: - self._read_env_var(read_config, k) + for (k, v) in variables.items(): + self._read_env_var(read_config, k, v) return read_config @@ -83,9 +86,13 @@ class Configuration: ): Utils.die("%s %s" % ('CHEAT_HIGHLIGHT must be one of:', colors)) - def _read_env_var(self, current_config, key): - if (os.environ.get(key)): + 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') @@ -96,17 +103,32 @@ class Configuration: 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._saved_configuration.get('DEFAULT_CHEAT_DIR') + return self._choose_value('CHEAT_DEFAULT_DIR', 'DEFAULT_CHEAT_DIR') def get_cheatpath(self): - return self._saved_configuration.get('CHEATPATH') + return self._choose_value('CHEAT_PATH', 'CHEATPATH') def get_cheatcolors(self): - return self._saved_configuration.get('CHEATCOLORS') + return self._choose_value('CHEAT_COLORS', 'CHEATCOLORS') def get_editor(self): - return self._saved_configuration.get('EDITOR') + return self._choose_value('CHEAT_EDITOR', 'EDITOR') def get_highlight(self): return self._saved_configuration.get('CHEAT_HIGHLIGHT') diff --git a/cheat/utils.py b/cheat/utils.py index 59d062e..e9d79e9 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -32,8 +32,11 @@ class Utils: 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 ["True", "true", "1", 1] or + if (self._displaycolors not in positive_values or not sys.stdout.isatty()): return sheet_content diff --git a/config/cheat b/config/cheat index 3a7537b..d45ba0c 100644 --- a/config/cheat +++ b/config/cheat @@ -1 +1 @@ -{"CHEATCOLORS":true,"EDITOR":"vi"} +{"CHEAT_COLORS":true,"CHEAT_EDITOR":"vi"}