Performed a large refactoring

Performed an extensive refactoring on the entire application for the
sake of code-cleanliness.

- Refactored out of an ad-hoc Imperative paradigm into more of a
  functional/declarative paradigm. IMO, this makes the application
  signifcantly easier to understand.

- Moved away from `argparse` and into `docopt` for argument parsing

- Version bump to 2.0.0

- Performed extensive refactoring on the setup.py script. Script should
  install to the system more cleanly now.

- Made minor formatting changes to the --list flag output

- Updated the README

Squashed commit of the following:

commit e5681bd536aa0220cdeb7884cc248db55be408c9
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Sat Apr 26 23:30:21 2014 -0400

    Fixed many bugs

    Everything seems to work now, I think.

commit 764ec5950cee958eb1b8333ddfcb6bcd45c28429
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Sat Apr 26 21:51:31 2014 -0400

    Restructuring for the sake of setup.py

    Seem to finally have a working install script

commit 5a866c23857b77ec65070dd8023cd734f2b7c242
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Sat Apr 26 18:01:11 2014 -0400

    Nits

commit a79954ba5b33d992fa6a32abffb33b161d624e3d
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Sat Apr 26 17:53:03 2014 -0400

    Implemented search

commit b570a897e9a12c15affe1a72628deae31836dee2
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Sat Apr 26 17:11:27 2014 -0400

    Nits

commit 1a8d85b44457f1b2131b3e8475c5270b5d0899e3
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Sat Apr 26 17:02:22 2014 -0400

    Still refactoring across files

    Trying to make the program structure clearer

commit 34dffd6462e492e81ea558e2009a71051b7663c9
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Sat Apr 26 16:40:37 2014 -0400

    Breaking app into several files

    This is for the sake of code-cleanliness

commit 4825d678ff5f9817ccbf727ef71e5dea15ff2586
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Sat Apr 26 15:55:19 2014 -0400

    Got syntax highlighting working

commit c37d7a626d451bfca3d4a072eb9fed604085170f
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Sat Apr 26 15:29:22 2014 -0400

    Reduced verbosity of function names

commit 8e626045186b37dce2480f5af1994ddfa8db79b5
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Sat Apr 26 15:24:41 2014 -0400

    Refactored argument passing

    Fewer arguments now need to be passed throughout the app.

commit 807ba814650010b3dd1b59d27400b3fb4fcfede7
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Sat Apr 26 11:40:05 2014 -0400

    Working through the refactor

commit e34e6540d4f8cd727e98aac68289d515a02d5fe6
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Thu Apr 24 20:00:10 2014 -0400

    Got a basic end-to-end refactor working

    Have re-implemented just the most basic functionality in the "cheat2"
    file.
This commit is contained in:
Chris Lane committed 2014-04-26 23:39:19 -04:00
1 parent c03cca9298
commit f46698b656
105 files changed
+337 -388

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
Changelog
=========
+10 -44
View File
@@ -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.
Executable
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env python
"""cheat
Usage:
cheat <cheatsheet>
cheat -e <cheatsheet>
cheat -s <keyword>
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 <keyword>
-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['<cheatsheet>'])
# search among the cheatsheets
elif options['--search']:
print(colorize(sheets.search(options['<keyword>'])))
# print the cheatsheet
else:
print(colorize(sheet.read(options['<cheatsheet>'])))
-320
View File
@@ -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 <http://www.gnu.org/licenses/>.
"""
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 <cheatsheet>` '
'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 <cheatseet>')
parser_group.add_argument('-e', '--edit', metavar='cheatsheet',
action=EditSheet, type=str, nargs=1,
help='Edit <cheatsheet>')
parser_group.add_argument('-s', '--search', metavar='term',
action=SearchSheet, type=str, nargs=1,
help='Search <term> 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()
+3
View File
@@ -0,0 +1,3 @@
import sheet
import sheets
import utils
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+4
View File
@@ -0,0 +1,4 @@
import os
def sheets_dir():
return os.path.split(__file__)
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+94
View File
@@ -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()
Loaded 100 of 105 files, more files were not shown because too many files have changed in this diff. Show more