diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..38ff556 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,3 @@ +Changelog +========= + diff --git a/README.md b/README.md index 8d95670..dd7a30e 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ remember. ![The obligatory xkcd](http://imgs.xkcd.com/comics/tar.png 'The obligatory xkcd') -`cheat` depends only on python. +`cheat` depends only on `python` and `pip`. Examples @@ -15,9 +15,7 @@ Examples The next time you're forced to disarm a nuclear weapon without consulting Google, you may run: -```sh -cheat tar -``` + cheat tar You will be presented with a cheatsheet resembling: @@ -38,7 +36,7 @@ tar -xjvf /path/to/foo.tgz tar -cjvf /path/to/foo.tgz /path/to/foo/ ``` -To see what cheatsheets are availble, run `cheat` with no arguments. +To see what cheatsheets are availble, run `cheat -l`. Note that, while `cheat` was designed primarily for *nix system administrators, it is agnostic as to what content it stores. If you would like to use `cheat` @@ -47,38 +45,14 @@ to store notes on your favorite cookie recipes, feel free. Installing ========== +First install the required python dependencies with: -### Installing for all users (requires root) + sudo pip install docopt pygments -Clone this repository and `cd` into it, then run +Then, clone this repository, `cd` into it, and run: sudo python setup.py install -### Installing in your home directory - -Clone this repository and `cd` into it, then run - - mkdir -p ~/bin - cp cheat ~/bin - mkdir ~/.cheat - cp cheatsheets/* ~/.cheat - -### Testing - -After installing for all users or in your home directory, try `cheat tar` for instance. - -### Troubleshooting - -In case you got an error such as: -> ImportError: No module named argparse - -You're probably using python < 2.7 and you need to manually install the argparse module. -You can do this easily with pip: -```bash -sudo apt-get install python-pip -sudo pip install argparse -``` -Other methods: https://pypi.python.org/pypi/argparse Modifying Cheatsheets ===================== @@ -118,15 +92,11 @@ Setting a CHEATPATH You can additionally instruct `cheat` to look for cheatsheets in other directories by exporting a `CHEATPATH` environment variable: -```bash -export CHEATPATH=/path/to/my/cheats -``` + export CHEATPATH=/path/to/my/cheats You may, of course, append multiple directories to your `CHEATPATH`: -```bash -export CHEATPATH=$CHEATPATH:/path/to/more/cheats -``` + export CHEATPATH=$CHEATPATH:/path/to/more/cheats You may view which directories are on your `CHEATPATH` with `cheat -d`. @@ -135,18 +105,14 @@ Enabling Syntax Highlighting `cheat` can apply syntax highlighting to your cheatsheets if so desired. To enable this feature, set a `CHEATCOLORS` environment variable: -```bash -export CHEATCOLORS=true -``` + export CHEATCOLORS=true Creating/Editing Cheatsheets ---------------------------- Provided that you have an `EDITOR` environment variable set, you may create new cheatsheets via: -```bash -cheat -e foo -``` + cheat -e foo If the 'foo' cheatsheet already exists, it will be opened for editing. diff --git a/bin/cheat b/bin/cheat new file mode 100755 index 0000000..65dada2 --- /dev/null +++ b/bin/cheat @@ -0,0 +1,61 @@ +#!/usr/bin/env python + +"""cheat + +Usage: + cheat + cheat -e + cheat -s + cheat -l + cheat -d + cheat -v + +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. + +Examples: + To look up 'tar': + cheat tar + + To create or edit the cheatsheet for 'foo': + cheat -e foo + +Options: + -d --directories List directories on CHEATPATH + -e --edit Edit cheatsheet + -l --list List cheatsheets + -s --search Search cheatsheets for + -v --version Print the version number +""" + +# require the dependencies +from cheat import * +from cheat.utils import * +from docopt import docopt + + +if __name__ == '__main__': + # parse the command-line options + options = docopt(__doc__, version='cheat 2.0.0') + + # list directories + if options['--directories']: + print("\n".join(sheets.paths())) + + # list cheatsheets + elif options['--list']: + print(sheets.list()) + + # create/edit cheatsheet + elif options['--edit']: + sheet.create_or_edit(options['']) + + # search among the cheatsheets + elif options['--search']: + print(colorize(sheets.search(options['']))) + + # print the cheatsheet + else: + print(colorize(sheet.read(options['']))) diff --git a/cheat b/cheat deleted file mode 100755 index 03f4dd8..0000000 --- a/cheat +++ /dev/null @@ -1,320 +0,0 @@ -#!/usr/bin/env python -""" -cheat.py -- 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. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -""" - -import os -import sys -import argparse -import subprocess -from textwrap import dedent - -DEFAULT_CHEAT_DIR = (os.environ.get('DEFAULT_CHEAT_DIR') or - os.path.join(os.path.expanduser('~'), '.cheat')) -USE_PYGMENTS = False - -# NOTE remove this check if it is confirmed to work on windows -if os.name == 'posix' and 'CHEATCOLORS' in os.environ: - try: - from pygments import highlight - from pygments.util import ClassNotFound - from pygments.lexers import get_lexer_for_filename, TextLexer - from pygments.formatters import TerminalFormatter - USE_PYGMENTS = True - except ImportError: - pass - - -def pretty_print(filename): - """Applies syntax highlighting to a cheatsheet and writes it to stdout""" - try: - if os.path.splitext(filename)[1]: - lexer = get_lexer_for_filename(filename) - else: - # shell is a sensible default when there is no extension - lexer = get_lexer_for_filename(filename + '.sh') - - except ClassNotFound: - lexer = TextLexer() - - with open(filename) as istream: - code = istream.read() - - fmt = TerminalFormatter() - highlight(code, lexer, fmt, sys.stdout) - - -class CheatSheets(object): - """Cheatsheets database class.""" - dirs = None - sheets = None - - def __init__(self): - self.dirs = self.__cheat_directories() - # verify that we have at least one cheat directory - if not self.dirs: - error_msg = ('The {default} dir does not exist' - ' or the CHEATPATH var is not set.') - print >> sys.stderr, error_msg.format(default=DEFAULT_CHEAT_DIR) - exit(1) - self.sheets = self.__cheat_files() - - def __cheat_directories(self): - """Assembles a list of directories containing cheatsheets.""" - default_directories = [DEFAULT_CHEAT_DIR] - try: - import cheatsheets - default_directories.append(cheatsheets.cheat_dir) - except ImportError: - pass - - default = [default_dir for default_dir in default_directories - if os.path.isdir(default_dir)] - - if 'CHEATPATH' in os.environ and os.environ['CHEATPATH']: - return [path for path in os.environ['CHEATPATH'].split(os.pathsep) - if os.path.isdir(path)] + default - else: - return default - - def __cheat_files(self): - """ - Assembles a dictionary of cheatsheets found in the above directories. - """ - cheats = {} - for cheat_dir in reversed(self.dirs): - cheats.update(dict([(cheat, cheat_dir) - for cheat in os.listdir(cheat_dir) - if not cheat.startswith('.') - and not cheat.startswith('__')])) - return cheats - - def edit(self, cheat): - """Creates or edits a cheatsheet""" - - # Assert that the EDITOR environment variable is set and that at least - # 3 arguments have been given - if 'EDITOR' not in os.environ: - print >> sys.stderr, ('In order to create/edit a cheatsheet you ' - 'must set your EDITOR environment variable ' - 'to your favorite editor\'s path.') - exit(1) - elif os.environ['EDITOR'] == "": - print >> sys.stderr, ('Your EDITOR environment variable is set ' - 'to nothing, in order to create/edit a ' - 'cheatsheet your must set it to a valid ' - 'editor\'s path.') - exit(1) - else: - editor = os.environ['EDITOR'].split() - - # if the cheatsheet already exists, open it for editing - try: - if cheat in self.sheets: - sheet_path = os.path.join(self.sheets[cheat], cheat) - if os.access(sheet_path, os.W_OK): - subprocess.call(editor + [sheet_path]) - else: - print >> sys.stderr, ("Sheet '%s' [%s] is not editable." - % (cheat, sheet_path)) - print ('Do you want to ' - 'copy it to your user cheatsheets directory [%s] ' - 'before editing?\nKeep in mind that your sheet ' - 'will always be used before system-wide one.' - % DEFAULT_CHEAT_DIR) - awn = raw_input('[y/n] ') - if awn != 'y': - print ('Ok, if you want to edit system-wide sheet, ' - 'please try `cheat -e ` ' - 'again with sudo.') - exit(1) - import shutil - - # attempt to copy the cheatsheet to DEFAULT_CHEAT_DIR - try: - new_sheet = os.path.join(DEFAULT_CHEAT_DIR, cheat) - shutil.copy(sheet_path, new_sheet) - subprocess.call(editor + [new_sheet]) - - # fail gracefully if the cheatsheet cannot be copied. This - # can happen if DEFAULT_CHEAT_DIR does not exist - except IOError: - print ('Could not copy cheatsheet for editing.') - exit(1) - - # otherwise, create it - else: - import cheatsheets as cs - # Attempt to write the new cheatsheet to the user's ~/.cheat - # dir if it exists. If it does not exist, attempt to create it. - if os.access(DEFAULT_CHEAT_DIR, os.W_OK) or os.makedirs(DEFAULT_CHEAT_DIR): - subprocess.call(editor - + [os.path.join(DEFAULT_CHEAT_DIR, cheat)]) - - # If the directory cannot be created, write to the python - # package directory, though that will likely require the use - # of sudo - else: - if os.access(sheet_path, os.W_OK): - subprocess.call(editor - + [os.path.join(cs.cheat_dir, cheat)]) - else: - error_msg = ("Couldn't create '%s' cheatsheet.\n" - "Please retry usig sudo." % cheat) - print >> sys.stderr, error_msg - exit(1) - except OSError as errno: - print >> sys.stderr, ("Could not launch `%s` as your editor : %s" - % (editor[0], errno.strerror)) - exit(1) - - def list(self): - """Lists the cheatsheets that are currently available""" - max_command = max([len(x) for x in self.sheets.keys()]) + 3 - return ('\n'.join(sorted(['%s [%s]' % (key.ljust(max_command), value) - for key, value in self.sheets.items()]))) - - def __parse_cheat_command_block(self, cheat): - """Parse text blocks inside specified sheet file""" - block = "" - path = os.path.join(self.sheets[cheat], cheat) - with open(path) as cheat_fp: - for line in cheat_fp.readlines(): - if line == '\n': - if block: - yield block - block = "" - else: - block += line - if block: - yield block - - def search(self, term): - """Search for a term in sheetcheats""" - for cheat in self.sheets.keys(): - output = '' - for block in self.__parse_cheat_command_block(cheat): - if term in block: - if not output: - output = cheat + ":\n" - output += ''.join([" " + line + '\n' for line - in block.split('\n')]) - if output: - sys.stdout.write(output); - - -# Custom action for argparse -class ListDirectories(argparse.Action): - """List cheat directories and exit""" - def __call__(self, parser, namespace, values, option_string=None): - print("\n".join(sheets.dirs)) - parser.exit() - - -class ListCheatsheets(argparse.Action): - """List cheatsheets and exit""" - def __call__(self, parser, namespace, values, option_string=None): - print(sheets.list()); - parser.exit() - - -class EditSheet(argparse.Action): - """If the user wants to edit a cheatsheet""" - def __call__(self, parser, namespace, values, option_string=None): - sheets.edit(values[0]) - parser.exit() - - -class SearchSheet(argparse.Action): - """If the user wants to search a term inside all cheatsheets""" - def __call__(self, parser, namespace, values, option_string=None): - sheets.search(values[0]) - parser.exit() - - -def main(): - """Main execution function""" - global sheets - sheets = CheatSheets() - - desc = dedent(''' - 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.''').strip() - - epi = dedent(''' - Examples: - - To look up 'tar': - cheat tar - - To create or edit the cheatsheet for 'foo': - cheat -e foo - - To list the directories on the CHEATPATH - cheat -d - - To list the available cheatsheets: - cheat -l - ''').strip() - - parser = argparse.ArgumentParser(prog='cheat', - description=desc, epilog=epi, - formatter_class=argparse. - RawDescriptionHelpFormatter) - parser_group = parser.add_mutually_exclusive_group() - parser_group.add_argument('sheet', metavar='cheatsheet', - action='store', type=str, nargs='?', - help='Look at ') - parser_group.add_argument('-e', '--edit', metavar='cheatsheet', - action=EditSheet, type=str, nargs=1, - help='Edit ') - parser_group.add_argument('-s', '--search', metavar='term', - action=SearchSheet, type=str, nargs=1, - help='Search inside all cheatsheets') - parser_group.add_argument('-l', '--list', - action=ListCheatsheets, nargs=0, - help='List all available cheatsheets') - parser_group.add_argument('-d', '--cheat-directories', - action=ListDirectories, nargs=0, - help='List all current cheat dirs') - args = parser.parse_args() - sheet = args.sheet - - # Print the cheatsheet if it exists - if not sheet or sheet in ['help', 'cheat']: - parser.print_help() - elif sheet in sheets.sheets: - filename = os.path.join(sheets.sheets[sheet], sheet) - if USE_PYGMENTS: - pretty_print(filename) - else: - with open(filename) as istream: - for line in istream: - sys.stdout.write(line) - - # if it does not, say so - else: - print >> sys.stderr, ('No cheatsheet found for %s.' % sheet) - exit(1) - exit() - -if __name__ == '__main__': - main() diff --git a/cheat/__init__.py b/cheat/__init__.py new file mode 100644 index 0000000..50b5a16 --- /dev/null +++ b/cheat/__init__.py @@ -0,0 +1,3 @@ +import sheet +import sheets +import utils diff --git a/autocompletion/_cheat.zsh b/cheat/autocompletion/_cheat.zsh similarity index 100% rename from autocompletion/_cheat.zsh rename to cheat/autocompletion/_cheat.zsh diff --git a/autocompletion/cheat.bash b/cheat/autocompletion/cheat.bash similarity index 100% rename from autocompletion/cheat.bash rename to cheat/autocompletion/cheat.bash diff --git a/autocompletion/cheat.fish b/cheat/autocompletion/cheat.fish similarity index 100% rename from autocompletion/cheat.fish rename to cheat/autocompletion/cheat.fish diff --git a/cheatsheets/7z b/cheat/cheatsheets/7z similarity index 100% rename from cheatsheets/7z rename to cheat/cheatsheets/7z diff --git a/cheat/cheatsheets/__init__.py b/cheat/cheatsheets/__init__.py new file mode 100644 index 0000000..7a6d5a1 --- /dev/null +++ b/cheat/cheatsheets/__init__.py @@ -0,0 +1,4 @@ +import os + +def sheets_dir(): + return os.path.split(__file__) diff --git a/cheatsheets/ab b/cheat/cheatsheets/ab similarity index 100% rename from cheatsheets/ab rename to cheat/cheatsheets/ab diff --git a/cheatsheets/apk b/cheat/cheatsheets/apk similarity index 100% rename from cheatsheets/apk rename to cheat/cheatsheets/apk diff --git a/cheatsheets/apparmor b/cheat/cheatsheets/apparmor similarity index 100% rename from cheatsheets/apparmor rename to cheat/cheatsheets/apparmor diff --git a/cheatsheets/apt-cache b/cheat/cheatsheets/apt-cache similarity index 100% rename from cheatsheets/apt-cache rename to cheat/cheatsheets/apt-cache diff --git a/cheatsheets/apt-get b/cheat/cheatsheets/apt-get similarity index 100% rename from cheatsheets/apt-get rename to cheat/cheatsheets/apt-get diff --git a/cheatsheets/aptitude b/cheat/cheatsheets/aptitude similarity index 100% rename from cheatsheets/aptitude rename to cheat/cheatsheets/aptitude diff --git a/cheatsheets/asciiart b/cheat/cheatsheets/asciiart similarity index 100% rename from cheatsheets/asciiart rename to cheat/cheatsheets/asciiart diff --git a/cheatsheets/asterisk b/cheat/cheatsheets/asterisk similarity index 100% rename from cheatsheets/asterisk rename to cheat/cheatsheets/asterisk diff --git a/cheatsheets/at b/cheat/cheatsheets/at similarity index 100% rename from cheatsheets/at rename to cheat/cheatsheets/at diff --git a/cheatsheets/awk b/cheat/cheatsheets/awk similarity index 100% rename from cheatsheets/awk rename to cheat/cheatsheets/awk diff --git a/cheatsheets/bash b/cheat/cheatsheets/bash similarity index 100% rename from cheatsheets/bash rename to cheat/cheatsheets/bash diff --git a/cheatsheets/chmod b/cheat/cheatsheets/chmod similarity index 100% rename from cheatsheets/chmod rename to cheat/cheatsheets/chmod diff --git a/cheatsheets/chown b/cheat/cheatsheets/chown similarity index 100% rename from cheatsheets/chown rename to cheat/cheatsheets/chown diff --git a/cheatsheets/convert b/cheat/cheatsheets/convert similarity index 100% rename from cheatsheets/convert rename to cheat/cheatsheets/convert diff --git a/cheatsheets/crontab b/cheat/cheatsheets/crontab similarity index 100% rename from cheatsheets/crontab rename to cheat/cheatsheets/crontab diff --git a/cheatsheets/curl b/cheat/cheatsheets/curl similarity index 100% rename from cheatsheets/curl rename to cheat/cheatsheets/curl diff --git a/cheatsheets/cut b/cheat/cheatsheets/cut similarity index 100% rename from cheatsheets/cut rename to cheat/cheatsheets/cut diff --git a/cheatsheets/date b/cheat/cheatsheets/date similarity index 100% rename from cheatsheets/date rename to cheat/cheatsheets/date diff --git a/cheatsheets/dd b/cheat/cheatsheets/dd similarity index 100% rename from cheatsheets/dd rename to cheat/cheatsheets/dd diff --git a/cheatsheets/df b/cheat/cheatsheets/df similarity index 100% rename from cheatsheets/df rename to cheat/cheatsheets/df diff --git a/cheatsheets/dhclient b/cheat/cheatsheets/dhclient similarity index 100% rename from cheatsheets/dhclient rename to cheat/cheatsheets/dhclient diff --git a/cheatsheets/diff b/cheat/cheatsheets/diff similarity index 100% rename from cheatsheets/diff rename to cheat/cheatsheets/diff diff --git a/cheatsheets/distcc b/cheat/cheatsheets/distcc similarity index 100% rename from cheatsheets/distcc rename to cheat/cheatsheets/distcc diff --git a/cheatsheets/emacs b/cheat/cheatsheets/emacs similarity index 100% rename from cheatsheets/emacs rename to cheat/cheatsheets/emacs diff --git a/cheatsheets/find b/cheat/cheatsheets/find similarity index 100% rename from cheatsheets/find rename to cheat/cheatsheets/find diff --git a/cheatsheets/gcc b/cheat/cheatsheets/gcc similarity index 100% rename from cheatsheets/gcc rename to cheat/cheatsheets/gcc diff --git a/cheatsheets/gdb b/cheat/cheatsheets/gdb similarity index 100% rename from cheatsheets/gdb rename to cheat/cheatsheets/gdb diff --git a/cheatsheets/git b/cheat/cheatsheets/git similarity index 100% rename from cheatsheets/git rename to cheat/cheatsheets/git diff --git a/cheatsheets/gpg b/cheat/cheatsheets/gpg similarity index 100% rename from cheatsheets/gpg rename to cheat/cheatsheets/gpg diff --git a/cheatsheets/grep b/cheat/cheatsheets/grep similarity index 100% rename from cheatsheets/grep rename to cheat/cheatsheets/grep diff --git a/cheatsheets/gs b/cheat/cheatsheets/gs similarity index 100% rename from cheatsheets/gs rename to cheat/cheatsheets/gs diff --git a/cheatsheets/head b/cheat/cheatsheets/head similarity index 100% rename from cheatsheets/head rename to cheat/cheatsheets/head diff --git a/cheatsheets/history b/cheat/cheatsheets/history similarity index 100% rename from cheatsheets/history rename to cheat/cheatsheets/history diff --git a/cheatsheets/ifconfig b/cheat/cheatsheets/ifconfig similarity index 100% rename from cheatsheets/ifconfig rename to cheat/cheatsheets/ifconfig diff --git a/cheatsheets/indent b/cheat/cheatsheets/indent similarity index 100% rename from cheatsheets/indent rename to cheat/cheatsheets/indent diff --git a/cheatsheets/iptables b/cheat/cheatsheets/iptables similarity index 100% rename from cheatsheets/iptables rename to cheat/cheatsheets/iptables diff --git a/cheatsheets/irssi b/cheat/cheatsheets/irssi similarity index 100% rename from cheatsheets/irssi rename to cheat/cheatsheets/irssi diff --git a/cheatsheets/iwconfig b/cheat/cheatsheets/iwconfig similarity index 100% rename from cheatsheets/iwconfig rename to cheat/cheatsheets/iwconfig diff --git a/cheatsheets/journalctl b/cheat/cheatsheets/journalctl similarity index 100% rename from cheatsheets/journalctl rename to cheat/cheatsheets/journalctl diff --git a/cheatsheets/less b/cheat/cheatsheets/less similarity index 100% rename from cheatsheets/less rename to cheat/cheatsheets/less diff --git a/cheatsheets/ln b/cheat/cheatsheets/ln similarity index 100% rename from cheatsheets/ln rename to cheat/cheatsheets/ln diff --git a/cheatsheets/ls b/cheat/cheatsheets/ls similarity index 100% rename from cheatsheets/ls rename to cheat/cheatsheets/ls diff --git a/cheatsheets/lsof b/cheat/cheatsheets/lsof similarity index 100% rename from cheatsheets/lsof rename to cheat/cheatsheets/lsof diff --git a/cheatsheets/markdown b/cheat/cheatsheets/markdown similarity index 100% rename from cheatsheets/markdown rename to cheat/cheatsheets/markdown diff --git a/cheatsheets/mkdir b/cheat/cheatsheets/mkdir similarity index 100% rename from cheatsheets/mkdir rename to cheat/cheatsheets/mkdir diff --git a/cheatsheets/mount b/cheat/cheatsheets/mount similarity index 100% rename from cheatsheets/mount rename to cheat/cheatsheets/mount diff --git a/cheatsheets/mysql b/cheat/cheatsheets/mysql similarity index 100% rename from cheatsheets/mysql rename to cheat/cheatsheets/mysql diff --git a/cheatsheets/mysqldump b/cheat/cheatsheets/mysqldump similarity index 100% rename from cheatsheets/mysqldump rename to cheat/cheatsheets/mysqldump diff --git a/cheatsheets/ncat b/cheat/cheatsheets/ncat similarity index 100% rename from cheatsheets/ncat rename to cheat/cheatsheets/ncat diff --git a/cheatsheets/netstat b/cheat/cheatsheets/netstat similarity index 100% rename from cheatsheets/netstat rename to cheat/cheatsheets/netstat diff --git a/cheatsheets/nmap b/cheat/cheatsheets/nmap similarity index 100% rename from cheatsheets/nmap rename to cheat/cheatsheets/nmap diff --git a/cheatsheets/notify-send b/cheat/cheatsheets/notify-send similarity index 100% rename from cheatsheets/notify-send rename to cheat/cheatsheets/notify-send diff --git a/cheatsheets/od b/cheat/cheatsheets/od similarity index 100% rename from cheatsheets/od rename to cheat/cheatsheets/od diff --git a/cheatsheets/openssl b/cheat/cheatsheets/openssl similarity index 100% rename from cheatsheets/openssl rename to cheat/cheatsheets/openssl diff --git a/cheatsheets/pacman b/cheat/cheatsheets/pacman similarity index 100% rename from cheatsheets/pacman rename to cheat/cheatsheets/pacman diff --git a/cheatsheets/pdftk b/cheat/cheatsheets/pdftk similarity index 100% rename from cheatsheets/pdftk rename to cheat/cheatsheets/pdftk diff --git a/cheatsheets/php b/cheat/cheatsheets/php similarity index 100% rename from cheatsheets/php rename to cheat/cheatsheets/php diff --git a/cheatsheets/ps b/cheat/cheatsheets/ps similarity index 100% rename from cheatsheets/ps rename to cheat/cheatsheets/ps diff --git a/cheatsheets/python b/cheat/cheatsheets/python similarity index 100% rename from cheatsheets/python rename to cheat/cheatsheets/python diff --git a/cheatsheets/rm b/cheat/cheatsheets/rm similarity index 100% rename from cheatsheets/rm rename to cheat/cheatsheets/rm diff --git a/cheatsheets/rsync b/cheat/cheatsheets/rsync similarity index 100% rename from cheatsheets/rsync rename to cheat/cheatsheets/rsync diff --git a/cheatsheets/sam2p b/cheat/cheatsheets/sam2p similarity index 100% rename from cheatsheets/sam2p rename to cheat/cheatsheets/sam2p diff --git a/cheatsheets/scp b/cheat/cheatsheets/scp similarity index 100% rename from cheatsheets/scp rename to cheat/cheatsheets/scp diff --git a/cheatsheets/screen b/cheat/cheatsheets/screen similarity index 100% rename from cheatsheets/screen rename to cheat/cheatsheets/screen diff --git a/cheatsheets/sed b/cheat/cheatsheets/sed similarity index 100% rename from cheatsheets/sed rename to cheat/cheatsheets/sed diff --git a/cheatsheets/shred b/cheat/cheatsheets/shred similarity index 100% rename from cheatsheets/shred rename to cheat/cheatsheets/shred diff --git a/cheatsheets/sockstat b/cheat/cheatsheets/sockstat similarity index 100% rename from cheatsheets/sockstat rename to cheat/cheatsheets/sockstat diff --git a/cheatsheets/sort b/cheat/cheatsheets/sort similarity index 100% rename from cheatsheets/sort rename to cheat/cheatsheets/sort diff --git a/cheatsheets/split b/cheat/cheatsheets/split similarity index 100% rename from cheatsheets/split rename to cheat/cheatsheets/split diff --git a/cheatsheets/sqlmap b/cheat/cheatsheets/sqlmap similarity index 100% rename from cheatsheets/sqlmap rename to cheat/cheatsheets/sqlmap diff --git a/cheatsheets/ssh b/cheat/cheatsheets/ssh similarity index 100% rename from cheatsheets/ssh rename to cheat/cheatsheets/ssh diff --git a/cheatsheets/ssh-copy-id b/cheat/cheatsheets/ssh-copy-id similarity index 100% rename from cheatsheets/ssh-copy-id rename to cheat/cheatsheets/ssh-copy-id diff --git a/cheatsheets/ssh-keygen b/cheat/cheatsheets/ssh-keygen similarity index 100% rename from cheatsheets/ssh-keygen rename to cheat/cheatsheets/ssh-keygen diff --git a/cheatsheets/stdout b/cheat/cheatsheets/stdout similarity index 100% rename from cheatsheets/stdout rename to cheat/cheatsheets/stdout diff --git a/cheatsheets/strace b/cheat/cheatsheets/strace similarity index 100% rename from cheatsheets/strace rename to cheat/cheatsheets/strace diff --git a/cheatsheets/systemctl b/cheat/cheatsheets/systemctl similarity index 100% rename from cheatsheets/systemctl rename to cheat/cheatsheets/systemctl diff --git a/cheatsheets/tail b/cheat/cheatsheets/tail similarity index 100% rename from cheatsheets/tail rename to cheat/cheatsheets/tail diff --git a/cheatsheets/tar b/cheat/cheatsheets/tar similarity index 100% rename from cheatsheets/tar rename to cheat/cheatsheets/tar diff --git a/cheatsheets/tcpdump b/cheat/cheatsheets/tcpdump similarity index 100% rename from cheatsheets/tcpdump rename to cheat/cheatsheets/tcpdump diff --git a/cheatsheets/tmux b/cheat/cheatsheets/tmux similarity index 100% rename from cheatsheets/tmux rename to cheat/cheatsheets/tmux diff --git a/cheatsheets/top b/cheat/cheatsheets/top similarity index 100% rename from cheatsheets/top rename to cheat/cheatsheets/top diff --git a/cheatsheets/truncate b/cheat/cheatsheets/truncate similarity index 100% rename from cheatsheets/truncate rename to cheat/cheatsheets/truncate diff --git a/cheatsheets/uname b/cheat/cheatsheets/uname similarity index 100% rename from cheatsheets/uname rename to cheat/cheatsheets/uname diff --git a/cheatsheets/vim b/cheat/cheatsheets/vim similarity index 100% rename from cheatsheets/vim rename to cheat/cheatsheets/vim diff --git a/cheatsheets/wget b/cheat/cheatsheets/wget similarity index 100% rename from cheatsheets/wget rename to cheat/cheatsheets/wget diff --git a/cheatsheets/xargs b/cheat/cheatsheets/xargs similarity index 100% rename from cheatsheets/xargs rename to cheat/cheatsheets/xargs diff --git a/cheatsheets/yaourt b/cheat/cheatsheets/yaourt similarity index 100% rename from cheatsheets/yaourt rename to cheat/cheatsheets/yaourt diff --git a/cheatsheets/youtube-dl b/cheat/cheatsheets/youtube-dl similarity index 100% rename from cheatsheets/youtube-dl rename to cheat/cheatsheets/youtube-dl diff --git a/cheatsheets/yum b/cheat/cheatsheets/yum similarity index 100% rename from cheatsheets/yum rename to cheat/cheatsheets/yum diff --git a/cheat/sheet.py b/cheat/sheet.py new file mode 100644 index 0000000..1038aef --- /dev/null +++ b/cheat/sheet.py @@ -0,0 +1,94 @@ +from cheat import sheets +from cheat import utils +from cheat.utils import * +import os +import shutil +import subprocess + + +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): + """ Creates or edits a cheatsheet """ + + # if the cheatsheet does not exist + if not exists(sheet): + create(sheet) + + # if the cheatsheet exists and is writeable... + elif exists(sheet) and is_writable(sheet): + edit(sheet) + + # if the cheatsheet exists but is not writable... + elif exists(sheet) and not is_writable(sheet): + # ... ask the user if we should copy the cheatsheet to her home directory for editing + yes = prompt_yes_or_no( + 'The ' + sheet + ' sheet is not editable. Do you want to copy it to ' + 'your user cheatsheets directory before editing? Keep in mind that ' + 'your sheet will always be used before system-wide one.' + ) + + # if yes, copy the cheatsheet to the home directory before editing + if yes: + copy(path(sheet), os.path.join(sheets.default_path(), sheet)) + edit(sheet) + + # if no, just abort + else: + die('Aborting.') + + +def create(sheet): + """ Creates a cheatsheet """ + new_sheet_path = os.path.join(sheets.default_path(), sheet) + + try: + subprocess.call([editor(), new_sheet_path]) + + except OSError: + die('Could not launch ' + editor()) + + +def edit(sheet): + """ Opens a cheatsheet for editing """ + + try: + subprocess.call([editor(), path(sheet)]) + + except OSError: + die('Could not launch ' + editor()) + + +def exists(sheet): + """ Predicate that returns true if the sheet exists """ + return sheet in sheets.get() and os.access(path(sheet), os.R_OK) + + +def is_writable(sheet): + """ Predicate that returns true if the sheet is writeable """ + return sheet in sheets.get() and os.access(path(sheet), os.W_OK) + + +def path(sheet): + """ Returns a sheet's filesystem path """ + return sheets.get()[sheet] + + +def read(sheet): + """ 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() diff --git a/cheat/sheets.py b/cheat/sheets.py new file mode 100644 index 0000000..82cdb3d --- /dev/null +++ b/cheat/sheets.py @@ -0,0 +1,76 @@ +from cheat import cheatsheets +from cheat.utils import * +import os + + +def default_path(): + """ Returns the default cheatsheet path """ + + # the default path becomes confused when cheat is run as root, so fail + # under those circumstances. (There is no good reason to need to run cheat + # as root.) + if os.geteuid() == 0: + die('Please do not run this application as root.'); + + return os.environ.get('DEFAULT_CHEAT_DIR') or os.path.join(os.path.expanduser('~'), '.cheat') + + +# @todo: memoize result +def get(): + """ Assembles a dictionary of cheatsheets as name => file-path """ + cheats = {} + for cheat_dir in reversed(paths()): + cheats.update( + dict([ + (cheat, os.path.join(cheat_dir, cheat)) + for cheat in os.listdir(cheat_dir) + if not cheat.startswith('.') + and not cheat.startswith('__') + ]) + ) + + return cheats + + +def paths(): + """ Assembles a list of directories containing cheatsheets """ + sheet_paths = [ + default_path(), + cheatsheets.sheets_dir()[0], + ] + + # merge the CHEATPATH paths into the sheet_paths + if 'CHEATPATH' in os.environ and os.environ['CHEATPATH']: + for path in os.environ['CHEATPATH'].split(os.pathsep): + if os.path.isdir(path): + sheet_paths.append(path) + + if not sheet_paths: + die('The DEFAULT_CHEAT_DIR dir does not exist or the CHEATPATH is not set.') + + return sheet_paths + + +def list(): + """ Lists the available cheatsheets """ + sheet_list = '' + pad_length = max([len(x) for x in get().keys()]) + 4 + for sheet in sorted(get().items()): + sheet_list += sheet[0].ljust(pad_length) + sheet[1] + "\n" + return sheet_list + + +def search(term): + """ Searches all cheatsheets for the specified term """ + result = '' + + for cheatsheet in sorted(get().items()): + match = '' + for line in open(cheatsheet[1]): + if term in line: + match += ' ' + line + + if not match == '': + result += cheatsheet[0] + ":\n" + match + "\n" + + return result diff --git a/cheat/test/__init__.py b/cheat/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cheat/utils.py b/cheat/utils.py new file mode 100644 index 0000000..7d57aba --- /dev/null +++ b/cheat/utils.py @@ -0,0 +1,56 @@ +import os +import sys + + +def colorize(sheet_content): + """ Colorizes cheatsheet content if so configured """ + + # only colorize if so configured + if not 'CHEATCOLORS' in os.environ: + return sheet_content + + try: + from pygments import highlight + from pygments.lexers import BashLexer + from pygments.formatters import TerminalFormatter + + # if pygments can't load, just return the uncolorized text + except ImportError: + return sheet_content + + return highlight(sheet_content, BashLexer(), TerminalFormatter()) + + +def die(message): + """ Prints a message to stderr and then terminates """ + warn (message) + exit(1) + + +def editor(): + """ Determines the user's preferred editor """ + if 'EDITOR' not in os.environ: + die( + 'In order to create/edit a cheatsheet you must set your EDITOR ' + 'environment variable to your editor\'s path.' + ) + + elif os.environ['EDITOR'] == "": + die( + 'Your EDITOR environment variable is set to an empty string. It must ' + 'be set to your editor\'s path.' + ) + + else: + return os.environ['EDITOR'] + + +def prompt_yes_or_no(question): + """ Prompts the user with a yes-or-no question """ + print(question) + return raw_input('[y/n] ') == 'y' + + +def warn(message): + """ Prints a message to stderr """ + print >> sys.stderr, (message) diff --git a/cheatsheets/__init__.py b/cheatsheets/__init__.py deleted file mode 100644 index 8a9b797..0000000 --- a/cheatsheets/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -import os - -cheat_dir, __init = os.path.split(__file__) diff --git a/setup.py b/setup.py index ec94f8f..20ab22d 100644 --- a/setup.py +++ b/setup.py @@ -1,24 +1,33 @@ -#!/usr/bin/env python - from distutils.core import setup import os -setup(name='cheat', - version='1.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=['cheatsheets'], - package_data={'cheatsheets': [f for f in os.listdir('cheatsheets') - if '.' not in f]}, - scripts=['cheat'], - data_files=[('/usr/share/zsh/site-functions', ['autocompletion/_cheat.zsh']), - ('/etc/bash_completion.d' , ['autocompletion/cheat.bash']), - ('/usr/share/fish/completions' , ['autocompletion/cheat.fish']) - ] - ) +setup( + name = 'cheat', + version = '2.0.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 = [ + 'cheat', + 'cheat.cheatsheets', + 'cheat.test', + ], + package_data = { + 'cheat.cheatsheets': [f for f in os.listdir('cheat/cheatsheets') if '.' not in f] + }, + scripts = ['bin/cheat'], + data_files = [ + ('/usr/share/zsh/site-functions', ['cheat/autocompletion/_cheat.zsh']), + ('/etc/bash_completion.d' , ['cheat/autocompletion/cheat.bash']), + ('/usr/share/fish/completions' , ['cheat/autocompletion/cheat.fish']) + ], + install_requires = [ + 'docopt >= 0.6.1', + 'pygments >= 1.6.0', + ] +)