diff --git a/README.md b/README.md index a9c86e7..ec2a354 100644 --- a/README.md +++ b/README.md @@ -83,13 +83,13 @@ with your [dotfiles][]. Configuring ----------- -### Setting a CHEAT_DEFAULT_DIR ### +### Setting a CHEAT_USER_DIR ### Personal cheatsheets are saved in the `~/.cheat` directory by default, but you -can specify a different default by exporting a `CHEAT_DEFAULT_DIR` environment +can specify a different default by exporting a `CHEAT_USER_DIR` environment variable: ```sh -export CHEAT_DEFAULT_DIR='/path/to/my/cheats' +export CHEAT_USER_DIR='/path/to/my/cheats' ``` ### Setting a CHEAT_PATH ### diff --git a/bin/cheat b/bin/cheat index 252e52a..7ced880 100755 --- a/bin/cheat +++ b/bin/cheat @@ -13,7 +13,7 @@ Usage: cheat -v Options: - -d --directories List directories on CHEATPATH + -d --directories List directories on $CHEAT_PATH -e --edit Edit cheatsheet -l --list List cheatsheets -s --search Search cheatsheets for @@ -36,27 +36,57 @@ Examples: # require the dependencies from __future__ import print_function -from cheat.sheets import Sheets -from cheat.sheet import Sheet -from cheat.utils import Utils +from cheat.colorize import Colorize from cheat.configuration import Configuration +from cheat.sheet import Sheet +from cheat.sheets import Sheets +from cheat.utils import Utils 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() + + # create the CHEAT_USER_DIR if it does not exist + if not os.path.isdir(config.cheat_user_dir): + try: + os.mkdir(config.cheat_user_dir) + + except OSError: + Utils.die("%s %s %s" % ( + 'Could not create CHEAT_USER_DIR (', + config.cheat_user_dir, + ')') + ) + + # assert that the CHEAT_USER_DIR is readable and writable + if not os.access(config.cheat_user_dir, os.R_OK): + Utils.die("%s %s %s" % ( + 'The CHEAT_USER_DIR (', + config.cheat_user_dir, + ') is not readable') + ) + if not os.access(config.cheat_user_dir, os.W_OK): + Utils.die("%s %s %s" % ( + 'The CHEAT_USER_DIR (', + config.cheat_user_dir, + ') is not writeable') + ) + + # bootsrap sheets = Sheets(config) - utils = Utils(config) - sheet = Sheet(sheets, utils) + sheet = Sheet(config, sheets) + colorize = Colorize(config) # list directories if options['--directories']: - print("\n".join(sheets.paths())) + print("\n".join(sheets.directories())) # list cheatsheets elif options['--list']: @@ -64,12 +94,12 @@ if __name__ == '__main__': # create/edit cheatsheet elif options['--edit']: - sheet.create_or_edit(options['']) + sheet.edit(options['']) # search among the cheatsheets elif options['--search']: - print(utils.colorize(sheets.search(options[''])), end="") + print(colorize.syntax(sheets.search(options[''])), end="") # print the cheatsheet else: - print(utils.colorize(sheet.read(options[''])), end="") + print(colorize.syntax(sheet.read(options[''])), end="") diff --git a/cheat/__init__.py b/cheat/__init__.py index 2560cf4..e69de29 100644 --- a/cheat/__init__.py +++ b/cheat/__init__.py @@ -1,4 +0,0 @@ -from . import sheet -from . import sheets -from . import utils -from . import configuration \ No newline at end of file diff --git a/cheat/colorize.py b/cheat/colorize.py new file mode 100644 index 0000000..7b18beb --- /dev/null +++ b/cheat/colorize.py @@ -0,0 +1,62 @@ +from __future__ import print_function +import sys + + +class Colorize: + + def __init__(self, config): + self._config = config + + def search(self, needle, haystack): + """ Colorizes search results matched within a line """ + + # if a highlight color is not configured, exit early + if not self._config.cheat_highlight: + 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, self._config.cheat_highlight)) + + def syntax(self, sheet_content): + """ Applies syntax highlighting """ + + # 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(): + 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()) diff --git a/cheat/configuration.py b/cheat/configuration.py index 1700624..4358a2f 100644 --- a/cheat/configuration.py +++ b/cheat/configuration.py @@ -1,134 +1,107 @@ -import os from cheat.utils import Utils import json +import os 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 = self._select([ + os.environ.get('CHEAT_GLOBAL_CONF_PATH'), + '/etc/cheat', + ]) + config_file_path_local = self._select([ + os.environ.get('CHEAT_LOCAL_CONF_PATH'), + 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([ + Utils.boolify(os.environ.get('CHEAT_COLORS')), + Utils.boolify(os.environ.get('CHEATCOLORS')), + Utils.boolify(config.get('CHEAT_COLORS')), + True, + ]) - return merged_config + # self.cheat_user_dir + self.cheat_user_dir = self._select([ + os.environ.get('CHEAT_USER_DIR'), + os.environ.get('CHEAT_DEFAULT_DIR'), + os.environ.get('DEFAULT_CHEAT_DIR'), + # TODO: XDG home? + os.path.expanduser( + os.path.expandvars(os.path.join('~', '.cheat')) + ), + ]) - def _read_configuration_file(self, path): - # Reads configuration file and returns list of set variables - read_config = {} - if (os.path.isfile(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, + ]) + if isinstance(self.cheat_highlight, str): + Utils.boolify(self.cheat_highlight) + + # 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 """ + 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): + """ Validates 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/editor.py b/cheat/editor.py new file mode 100644 index 0000000..c48351f --- /dev/null +++ b/cheat/editor.py @@ -0,0 +1,29 @@ +from __future__ import print_function +from cheat.utils import Utils +import subprocess + + +class Editor: + + def __init__(self, config): + self._config = config + + def editor(self): + """ Determines the user's preferred editor """ + + # assert that the editor is set + 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._config.cheat_editor + + def open(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()) diff --git a/cheat/sheet.py b/cheat/sheet.py index 81553a1..4cd3f96 100644 --- a/cheat/sheet.py +++ b/cheat/sheet.py @@ -1,79 +1,64 @@ +from cheat.editor import Editor +from cheat.utils import Utils import io import os import shutil -from cheat.utils import Utils - class Sheet: - def __init__(self, sheets, utils): + def __init__(self, config, sheets): + self._config = config + self._editor = Editor(config) self._sheets = sheets - self._utils = utils - 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 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.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): - """ 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): - """ 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)) + 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) + default_path = os.path.join(self._config.cheat_user_dir, sheet) return (sheet in self._sheets.get() and - os.access(default_path_sheet, os.R_OK)) + os.access(default_path, os.R_OK)) - 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)) - - def path(self, sheet): + def _path(self, sheet): """ Returns a sheet's filesystem path """ return self._sheets.get()[sheet] + def edit(self, sheet): + """ Creates or edits a cheatsheet """ + + # if the cheatsheet does not exist + if not self._exists(sheet): + new_path = os.path.join(self._config.cheat_user_dir, sheet) + self._editor.open(new_path) + + # 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): + try: + shutil.copy( + self._path(sheet), + os.path.join(self._config.cheat_user_dir, sheet) + ) + + # fail gracefully if the cheatsheet cannot be copied. This can + # happen if CHEAT_USER_DIR does not exist + except IOError: + Utils.die('Could not copy cheatsheet for editing.') + + self._editor.open(self._path(sheet)) + + # if it exists and is in the default path, then just open it + else: + self._editor.open(self._path(sheet)) + def read(self, sheet): """ Returns the contents of the cheatsheet as a String """ - if not self.exists(sheet): + if not self._exists(sheet): Utils.die('No cheatsheet found for ' + sheet) - with io.open(self.path(sheet), encoding='utf-8') as cheatfile: + with io.open(self._path(sheet), encoding='utf-8') as cheatfile: return cheatfile.read() diff --git a/cheat/sheets.py b/cheat/sheets.py index 201c87d..8458ea6 100644 --- a/cheat/sheets.py +++ b/cheat/sheets.py @@ -1,55 +1,34 @@ +from cheat.colorize import Colorize +from cheat.utils import Utils import io import os -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._utils = Utils(config) + self._config = config + self._colorize = Colorize(config) - def default_path(self): - """ Returns the default cheatsheet path """ + # Assembles a dictionary of cheatsheets as name => file-path + self._sheets = {} + sheet_paths = [ + config.cheat_user_dir + ] - # 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)) + # merge the CHEAT_PATH paths into the sheet_paths + if config.cheat_path: + for path in config.cheat_path.split(os.pathsep): + if os.path.isdir(path): + sheet_paths.append(path) - # 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: - 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 get(self): - """ Assembles a dictionary of cheatsheets as name => file-path """ - cheats = {} + if not sheet_paths: + Utils.die('The CHEAT_USER_DIR dir does not exist ' + + 'or the CHEAT_PATH is not set.') # otherwise, scan the filesystem - for cheat_dir in reversed(self.paths()): - cheats.update( + for cheat_dir in reversed(sheet_paths): + self._sheets.update( dict([ (cheat, os.path.join(cheat_dir, cheat)) for cheat in os.listdir(cheat_dir) @@ -58,26 +37,22 @@ class Sheets: ]) ) - return cheats - - def paths(self): + def directories(self): """ Assembles a list of directories containing cheatsheets """ sheet_paths = [ - self.default_path(), + self._config.cheat_user_dir, ] # 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 not sheet_paths: - Utils.die('The DEFAULT_CHEAT_DIR dir does not exist ' - + 'or the CHEATPATH is not set.') + for path in self._config.cheat_path.split(os.pathsep): + sheet_paths.append(path) return sheet_paths + def get(self): + """ Returns a dictionary of cheatsheets as name => file-path """ + return self._sheets + def list(self): """ Lists the available cheatsheets """ sheet_list = '' @@ -94,7 +69,7 @@ class Sheets: match = '' for line in io.open(cheatsheet[1], encoding='utf-8'): if term in line: - match += ' ' + self._utils.highlight(term, line) + match += ' ' + self._colorize.search(term, line) if match != '': result += cheatsheet[0] + ":\n" + match + "\n" diff --git a/cheat/utils.py b/cheat/utils.py index e9d79e9..c481a7c 100644 --- a/cheat/utils.py +++ b/cheat/utils.py @@ -1,100 +1,26 @@ from __future__ import print_function -import os -import subprocess 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() - - 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: - 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, self._highlight_color)) - - 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 - - # 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()) - @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 """ - - # 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 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 """ print((message), file=sys.stderr) + + @staticmethod + def boolify(value): + """ Type-converts 'true' and 'false' to Booleans """ + # if `value` is not a string, return it as-is + if not isinstance(value, str): + return value + + # otherwise, convert "true" and "false" to Boolean counterparts + return value.strip().lower() == "true" diff --git a/ci/lint.sh b/ci/lint.sh new file mode 100755 index 0000000..bfbb640 --- /dev/null +++ b/ci/lint.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +# Resolve the app root +SCRIPT=`realpath $0` +SCRIPTPATH=`dirname $SCRIPT` +APPROOT=`realpath "$SCRIPTPATH/.."` + +flake8 $APPROOT/setup.py +flake8 $APPROOT/bin/cheat +flake8 $APPROOT/cheat/*.py 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" } diff --git a/setup.py b/setup.py index 43cc96a..d6410f6 100644 --- a/setup.py +++ b/setup.py @@ -9,31 +9,31 @@ cheat_path = os.environ.get('CHEAT_PATH') or '/usr/share/cheat' # aggregate the systme-wide cheatsheets cheat_files = [] for f in os.listdir('cheat/cheatsheets/'): - cheat_files.append(os.path.join('cheat/cheatsheets/',f)) + cheat_files.append(os.path.join('cheat/cheatsheets/', f)) # specify build params setup( - name = 'cheat', - version = '2.4.2', - author = 'Chris Lane', - author_email = 'chris@chris-allen-lane.com', - license = 'GPL3', - description = 'cheat allows you to create and view interactive cheatsheets ' + name='cheat', + version='2.4.2', + author='Chris Lane', + author_email='chris@chris-allen-lane.com', + license='GPL3', + description='cheat allows you to create and view interactive cheatsheets ' 'on the command-line. It was designed to help remind *nix system ' 'administrators of options for commands that they use frequently, but not ' 'frequently enough to remember.', - url = 'https://github.com/chrisallenlane/cheat', - packages = [ + url='https://github.com/chrisallenlane/cheat', + packages=[ 'cheat', 'cheat.test', ], - scripts = ['bin/cheat'], - install_requires = [ + scripts=['bin/cheat'], + install_requires=[ 'docopt >= 0.6.1', 'pygments >= 1.6.0', 'termcolor >= 1.1.0', ], - data_files = [ + data_files=[ (cheat_path, cheat_files), ('/etc', ['config/cheat']), ],