Compare commits

...

30 Commits
2.4.1 ... 2.5.0

Author SHA1 Message Date
074dba6e99 v2.5.0
`minor` version bump to `2.5.0`.
2019-02-04 12:04:52 -05:00
9d1dd15387 Merge pull request #427 from cheat/issue-349
Issue #349
2019-02-04 11:57:35 -05:00
caf355f142 Issue #349
Implements support for terminals with light backgrounds via a new
`CHEAT_COLORSCHEME` envvar.
2019-02-04 11:56:00 -05:00
2728ce4757 Merge pull request #426 from cheat/vagrant
Updated Vagrantfile
2019-02-04 11:54:06 -05:00
6ae76799f7 Updated Vagrantfile
Modified `Vagrantfile` to build an Ubuntu environment rather than
Alpine, after the latter exhibited weird behavior.
2019-02-04 11:50:25 -05:00
0b523a769f Merge pull request #425 from cheat/vagrant
Added Vagrantfile
2019-02-04 10:41:26 -05:00
f29cf03b68 Added Vagrantfile
Added a `Vagrantfile` which builds an alpine-based environment that can
be used for development and testing.
2019-02-04 10:40:10 -05:00
4347114e19 Merge pull request #424 from JensKorte/patch-1
link changed, added details
2019-02-04 10:05:36 -05:00
edc67e7819 Updated links to README
Re-pathed links from `chrisallenlane/cheat` to `cheat/cheat` as
appropriate, following the move.
2019-02-04 10:03:19 -05:00
aa33a36491 Merge pull request #422 from cheat/refactor
Refactor
2019-02-04 09:58:24 -05:00
8aac10dd8b link changed, added details
The table isn't available any more in the recent wiki page. The new link uses the old version of wikipedia. In my browser I have to "unhide" the box.
2019-02-03 20:56:57 +01:00
9931b78c5f Lint
- Added instruction to lint `setup.py` to `ci/lint.sh`
- Updated `setup.py` per linter suggestions
2019-02-01 15:24:04 -05:00
a37577ee85 Trivial: docstrings
Updated some docstring comments.
2019-02-01 15:18:23 -05:00
3ad923eff0 Refactored (11)
Renamed `CHEAT_DEFAULT_DIR` to `CHEAT_USER_DIR` because the latter more
accurately describes the purpose of the variable.
2019-02-01 15:10:03 -05:00
ba47dc2cbc Refactored (10)
- Added `ci/lint.sh`, which uses `flake8` to lint the relevant files
- Made changes to appease the linter.
- Bugfix in `cheat/configuration` (missing dependency)
2019-02-01 14:44:48 -05:00
df21731c02 Trivial Python style corrections 2019-02-01 11:43:38 -05:00
a657699a24 Refactored (9)
Moved some functionality into the `Util` class.
2019-02-01 11:43:11 -05:00
5793c1845a Refactored (8)
Refactored `Sheet` class:

- Removed unnecessary indirection and extraneous methods
- Renamed some methods to conform to Pythonic conventions
- Renamed the `create_or_edit` method to `edit` to be consistent with
  subcommand name (`--edit`)
2019-02-01 11:23:38 -05:00
e2b5728283 Refactored (7)
Refactored for general code-clarity, with particular focus on removing
needless abstraction within `Sheet` and `Sheets` classes.
2019-01-31 20:03:56 -05:00
d61e4e7c34 Refactored (6)
Standardized (mostly) how the various classes are initialized.
2019-01-31 18:08:19 -05:00
145a81dcd6 Var renames
Replaced more references to deprecated envvar names to their newer
counterparts.
2019-01-31 17:55:26 -05:00
7c4fc54681 Refactored (5)
- Extracted `Colorize` class out of `Util` class. (The latter now only
  contains static methods.)
- Renamed methods in `Colorize` class for improved clarity.
- Refactored as necessary to accommodate the changes above.
2019-01-31 17:43:21 -05:00
878d7e7e1b Refactored (4)
Improved handling of edge-cases in `configuration.py`.
2019-01-31 17:40:53 -05:00
928637c9db Refactored (3)
Removed unnecessary `import` calls.
2019-01-31 17:14:21 -05:00
ab87bb11c4 Refactored (2)
Created an `Editor` class out methods in the `Util` class to enhance
code clarity.
2019-01-31 17:03:21 -05:00
8f757d7735 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
2019-01-31 16:45:28 -05:00
c922ef4c8d Version bump: 2.4.2
Contains `termcolor` dependency fix.
2019-01-29 11:45:43 -05:00
574a7ec3c9 Merge pull request #421 from chrisallenlane/termcolor
Added termcolor dependency to setup.py
2019-01-29 11:44:22 -05:00
5ae49228b7 Added termcolor dependency to setup.py
Added a missing `termcolor` dependency to `install_requires` in
`setup.py`. `termcolors` was introduced as an optional dependency when
the `CHEAT_HIGHLIGHT` envvar was implemented.
2019-01-29 11:42:14 -05:00
09acdf1a69 README edits
Updated the names of the (preferred) cheat-related environment
variables, which have been changed in recent versions.
2019-01-29 11:31:03 -05:00
15 changed files with 379 additions and 349 deletions

2
.gitignore vendored
View File

@ -1,5 +1,7 @@
*.log
*.pyc
.env
.vagrant
MANIFEST
build
cheat.egg-info

View File

@ -83,41 +83,48 @@ with your [dotfiles][].
Configuring
-----------
### Setting a DEFAULT_CHEAT_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 `DEFAULT_CHEAT_DIR` environment
can specify a different default by exporting a `CHEAT_USER_DIR` environment
variable:
```sh
export DEFAULT_CHEAT_DIR='/path/to/my/cheats'
export CHEAT_USER_DIR='/path/to/my/cheats'
```
### Setting a CHEATPATH ###
### Setting a CHEAT_PATH ###
You can additionally instruct `cheat` to look for cheatsheets in other
directories by exporting a `CHEATPATH` environment variable:
directories by exporting a `CHEAT_PATH` environment variable:
```sh
export CHEATPATH='/path/to/my/cheats'
export CHEAT_PATH='/path/to/my/cheats'
```
You may, of course, append multiple directories to your `CHEATPATH`:
You may, of course, append multiple directories to your `CHEAT_PATH`:
```sh
export CHEATPATH="$CHEATPATH:/path/to/more/cheats"
export CHEAT_PATH="$CHEAT_PATH:/path/to/more/cheats"
```
You may view which directories are on your `CHEATPATH` with `cheat -d`.
You may view which directories are on your `CHEAT_PATH` with `cheat -d`.
### Enabling Syntax Highlighting ###
`cheat` can optionally apply syntax highlighting to your cheatsheets. To
enable syntax highlighting, export a `CHEATCOLORS` environment variable:
enable syntax highlighting, export a `CHEAT_COLORS` environment variable:
```sh
export CHEATCOLORS=true
export CHEAT_COLORS=true
```
Note that [pygments][] must be installed on your system for this to work.
`cheat` ships with both light and dark colorschemes to support terminals with
different background colors. A colorscheme may be selected via the
`CHEAT_COLORSCHEME` envvar:
```sh
export CHEAT_COLORSCHEME=light # must be 'light' (default) or 'dark'
```
#### Specifying a Syntax Highlighter ####
You may manually specify which syntax highlighter to use for each cheatsheet by
@ -161,9 +168,9 @@ See Also:
- [Related Projects][related-projects]
[autocompletion]: https://github.com/chrisallenlane/cheat/wiki/Enabling-Command-line-Autocompletion
[autocompletion]: https://github.com/cheat/cheat/wiki/Enabling-Command-line-Autocompletion
[dotfiles]: http://dotfiles.github.io/
[gfm]: https://help.github.com/articles/creating-and-highlighting-code-blocks/
[installing]: https://github.com/chrisallenlane/cheat/wiki/Installing
[installing]: https://github.com/cheat/cheat/wiki/Installing
[pygments]: http://pygments.org/
[related-projects]: https://github.com/chrisallenlane/cheat/wiki/Related-Projects
[related-projects]: https://github.com/cheat/cheat/wiki/Related-Projects

17
Vagrantfile vendored Normal file
View File

@ -0,0 +1,17 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/bionic64"
config.vm.provider "virtualbox" do |vb|
vb.memory = "512"
end
config.vm.provision "shell", inline: <<-SHELL
sudo apt-get update
sudo apt-get install -y python-pip
su vagrant && sudo -H pip install docopt pygments termcolor flake8
cd /vagrant && sudo python setup.py install
SHELL
end

View File

@ -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 <keyword>
@ -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.1')
options = docopt(__doc__, version='cheat 2.5.0')
# 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['<cheatsheet>'])
sheet.edit(options['<cheatsheet>'])
# search among the cheatsheets
elif options['--search']:
print(utils.colorize(sheets.search(options['<keyword>'])), end="")
print(colorize.syntax(sheets.search(options['<keyword>'])), end="")
# print the cheatsheet
else:
print(utils.colorize(sheet.read(options['<cheatsheet>'])), end="")
print(colorize.syntax(sheet.read(options['<cheatsheet>'])), end="")

View File

@ -1,4 +0,0 @@
from . import sheet
from . import sheets
from . import utils
from . import configuration

View File

@ -19,5 +19,5 @@ youtube-dl -s example.com/watch?v=id
# To download audio in mp3 format with best quality available
youtube-dl --extract-audio --audio-format mp3 --audio-quality 0 example.com/watch?v=id
# For all video formats see
# http://en.wikipedia.org/wiki/YouTube#Quality_and_codecs
# For all video formats see link below (unfold "Comparison of YouTube media encoding options")
# https://en.wikipedia.org/w/index.php?title=YouTube&oldid=723160791#Quality_and_formats

65
cheat/colorize.py Normal file
View File

@ -0,0 +1,65 @@
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(bg=self._config.cheat_colorscheme))

View File

@ -1,134 +1,120 @@
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_colorscheme
self.cheat_colorscheme = self._select([
os.environ.get('CHEAT_COLORSCHEME'),
config.get('CHEAT_COLORSCHEME'),
'light',
]).strip().lower()
def _read_configuration_file(self, path):
# Reads configuration file and returns list of set variables
read_config = {}
if (os.path.isfile(path)):
# 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'))
),
])
# 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
# assert that the color scheme is valid
colorschemes = ['light', 'dark']
if self.cheat_colorscheme not in colorschemes:
Utils.die("%s %s" %
('CHEAT_COLORSCHEME must be one of:', colorschemes))
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

29
cheat/editor.py Normal file
View File

@ -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())

View File

@ -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()

View File

@ -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"

View File

@ -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"

10
ci/lint.sh Executable file
View File

@ -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

View File

@ -1,5 +1,6 @@
{
"CHEAT_COLORS": true,
"CHEAT_EDITOR":"vi",
"CHEAT_PATH":"/usr/share/cheat"
"CHEAT_COLORS" : true,
"CHEAT_COLORSCHEME" : "light",
"CHEAT_EDITOR" : "vi",
"CHEAT_PATH" : "/usr/share/cheat"
}

View File

@ -9,30 +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.1',
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.5.0',
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']),
],