mirror of
https://github.com/Erreur32/cheat.git
synced 2024-12-22 13:42:11 +01:00
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:
parent
c03cca9298
commit
f46698b656
105 changed files with 337 additions and 388 deletions
3
CHANGELOG
Normal file
3
CHANGELOG
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Changelog
|
||||||
|
=========
|
||||||
|
|
54
README.md
54
README.md
|
@ -7,7 +7,7 @@ remember.
|
||||||
|
|
||||||
![The obligatory xkcd](http://imgs.xkcd.com/comics/tar.png 'The obligatory xkcd')
|
![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
|
Examples
|
||||||
|
@ -15,9 +15,7 @@ Examples
|
||||||
The next time you're forced to disarm a nuclear weapon without consulting
|
The next time you're forced to disarm a nuclear weapon without consulting
|
||||||
Google, you may run:
|
Google, you may run:
|
||||||
|
|
||||||
```sh
|
cheat tar
|
||||||
cheat tar
|
|
||||||
```
|
|
||||||
|
|
||||||
You will be presented with a cheatsheet resembling:
|
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/
|
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,
|
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`
|
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
|
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
|
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
|
Modifying Cheatsheets
|
||||||
=====================
|
=====================
|
||||||
|
@ -118,15 +92,11 @@ Setting a CHEATPATH
|
||||||
You can additionally instruct `cheat` to look for cheatsheets in other
|
You can additionally instruct `cheat` to look for cheatsheets in other
|
||||||
directories by exporting a `CHEATPATH` environment variable:
|
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`:
|
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`.
|
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
|
`cheat` can apply syntax highlighting to your cheatsheets if so desired. To
|
||||||
enable this feature, set a `CHEATCOLORS` environment variable:
|
enable this feature, set a `CHEATCOLORS` environment variable:
|
||||||
|
|
||||||
```bash
|
export CHEATCOLORS=true
|
||||||
export CHEATCOLORS=true
|
|
||||||
```
|
|
||||||
|
|
||||||
Creating/Editing Cheatsheets
|
Creating/Editing Cheatsheets
|
||||||
----------------------------
|
----------------------------
|
||||||
Provided that you have an `EDITOR` environment variable set, you may create new
|
Provided that you have an `EDITOR` environment variable set, you may create new
|
||||||
cheatsheets via:
|
cheatsheets via:
|
||||||
|
|
||||||
```bash
|
cheat -e foo
|
||||||
cheat -e foo
|
|
||||||
```
|
|
||||||
|
|
||||||
If the 'foo' cheatsheet already exists, it will be opened for editing.
|
If the 'foo' cheatsheet already exists, it will be opened for editing.
|
||||||
|
|
||||||
|
|
61
bin/cheat
Executable file
61
bin/cheat
Executable 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
cheat
320
cheat
|
@ -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
cheat/__init__.py
Normal file
3
cheat/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import sheet
|
||||||
|
import sheets
|
||||||
|
import utils
|
4
cheat/cheatsheets/__init__.py
Normal file
4
cheat/cheatsheets/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
def sheets_dir():
|
||||||
|
return os.path.split(__file__)
|
94
cheat/sheet.py
Normal file
94
cheat/sheet.py
Normal 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()
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue