From b6e73539bd6e66eed396699b378538ab640154b2 Mon Sep 17 00:00:00 2001 From: 0rax Date: Fri, 11 Oct 2013 16:26:24 +0200 Subject: [PATCH 01/10] Added autocompletion for fish shell --- autocompletion/cheat.fish | 9 +++++++++ setup.py | 6 ++++-- 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 autocompletion/cheat.fish diff --git a/autocompletion/cheat.fish b/autocompletion/cheat.fish new file mode 100644 index 0000000..5dc1c70 --- /dev/null +++ b/autocompletion/cheat.fish @@ -0,0 +1,9 @@ +#completion for cheat +complete -c cheat -s h -l help -f -x --description "Display help and exit" +complete -c cheat -s e -l edit -f -x --description "Edit " +complete -c cheat -s l -l list -f -x --description "List all available cheatsheets" +complete -c cheat -s d -l cheat-directories -f -x --description "List all current cheat dirs" +complete -c cheat --authoritative -f +for cheatsheet in (cheat -l | cut -d' ' -f1) + complete -c cheat -a "$cheatsheet" +end diff --git a/setup.py b/setup.py index 663abbe..df3fb01 100644 --- a/setup.py +++ b/setup.py @@ -18,5 +18,7 @@ setup(name='cheat', if '.' not in f]}, scripts=['cheat'], data_files=[('/usr/share/zsh/site-functions', ['autocompletion/cheat.zsh']), - ('/etc/bash_completion.d' , ['autocompletion/cheat.bash'])] - ) + ('/etc/bash_completion.d' , ['autocompletion/cheat.bash']), + ('/usr/share/fish/completions' , ['autocompletion/cheat.fish']) + ] + ) From fc4b0479586d82fe62021b69d5672a4b9d539382 Mon Sep 17 00:00:00 2001 From: 0rax Date: Fri, 11 Oct 2013 18:32:21 +0200 Subject: [PATCH 02/10] In response to Issue #108: Added option to copy sheet while not editable. --- cheat | 87 ++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 25 deletions(-) diff --git a/cheat b/cheat index 6b296fb..01399b7 100755 --- a/cheat +++ b/cheat @@ -3,7 +3,7 @@ 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. + 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 @@ -25,9 +25,9 @@ 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 +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: @@ -40,8 +40,9 @@ if os.name == 'posix' and 'CHEATCOLORS' in os.environ: except ImportError: pass + def pretty_print(filename): - "Applies syntax highlighting to a cheatsheet and writes it to stdout" + """Applies syntax highlighting to a cheatsheet and writes it to stdout""" try: if os.path.splitext(filename)[1]: lexer = get_lexer_for_filename(filename) @@ -58,8 +59,9 @@ def pretty_print(filename): fmt = TerminalFormatter() highlight(code, lexer, fmt, sys.stdout) + class CheatSheets(object): - + dirs = None sheets = None @@ -67,7 +69,8 @@ class CheatSheets(object): 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.' + 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() @@ -91,7 +94,9 @@ class CheatSheets(object): return default def __cheat_files(self): - """Assembles a dictionary of cheatsheets found in the above directories.""" + """ + Assembles a dictionary of cheatsheets found in the above directories. + """ cheats = {} for cheat_dir in reversed(self.dirs): cheats.update(dict([(cheat, cheat_dir) @@ -103,37 +108,64 @@ class CheatSheets(object): 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 + # 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.') + '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.') + '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 sheets.sheets: - subprocess.call(editor + [os.path.join(self.sheets[cheat], cheat)]) + 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 + new_sheet = os.path.join(DEFAULT_CHEAT_DIR, cheat) + shutil.copy(sheet_path, new_sheet) + subprocess.call(editor + [new_sheet]) # 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. + # 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 + # If the directory cannot be created, write to the python + # package directory, though that will likely require the use + # of sudo else: - subprocess.call(editor + [os.path.join(cs.cheat_dir, cheat)]) + if os.access(sheet_path, os.W_OK): + subprocess.call(editor + [os.path.join(cs.cheat_dir, cheat)]) + else: + print >> sys.stderr, ("Couldn't create '%s' cheatsheet." + % cheat) + exit(1) except OSError, e: print >> sys.stderr, ("Could not launch `%s` as your editor : %s" % (editor[0], e.strerror)) @@ -145,6 +177,7 @@ class CheatSheets(object): return ('\n'.join(sorted(['%s [%s]' % (key.ljust(max_command), value) for key, value in self.sheets.items()]))) + # Custom action for argparse class ListDirectories(argparse.Action): """List cheat directories and exit""" @@ -152,18 +185,21 @@ class ListDirectories(argparse.Action): 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() + def main(): global sheets @@ -177,13 +213,13 @@ def main(): 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 @@ -193,7 +229,8 @@ def main(): parser = argparse.ArgumentParser(prog='cheat', description=desc, epilog=epi, - formatter_class=argparse.RawDescriptionHelpFormatter) + formatter_class=argparse. + RawDescriptionHelpFormatter) parser_group = parser.add_mutually_exclusive_group() parser_group.add_argument('sheet', metavar='cheatsheet', action='store', type=str, nargs='?', From 53978fa86fbaf6665ae16f09383655af0aeda078 Mon Sep 17 00:00:00 2001 From: 0rax Date: Fri, 11 Oct 2013 18:56:03 +0200 Subject: [PATCH 03/10] Add hint to use sudo when creation fail of sheet. --- cheat | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cheat b/cheat index 01399b7..120b764 100755 --- a/cheat +++ b/cheat @@ -163,7 +163,8 @@ class CheatSheets(object): if os.access(sheet_path, os.W_OK): subprocess.call(editor + [os.path.join(cs.cheat_dir, cheat)]) else: - print >> sys.stderr, ("Couldn't create '%s' cheatsheet." + print >> sys.stderr, ("Couldn't create '%s' cheatsheet.\n" + "Please retry usig sudo." % cheat) exit(1) except OSError, e: From b1df8fe3cc2bd16ee4eca071924dbee27e04c016 Mon Sep 17 00:00:00 2001 From: 0rax Date: Fri, 11 Oct 2013 19:15:07 +0200 Subject: [PATCH 04/10] fix edit autocomplete (just dont show description of -e/--edit option and i dont know why --- autocompletion/cheat.fish | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/autocompletion/cheat.fish b/autocompletion/cheat.fish index 5dc1c70..9c6b1ca 100644 --- a/autocompletion/cheat.fish +++ b/autocompletion/cheat.fish @@ -1,9 +1,12 @@ #completion for cheat complete -c cheat -s h -l help -f -x --description "Display help and exit" -complete -c cheat -s e -l edit -f -x --description "Edit " +complete -c cheat -l edit -f -x --description "Edit " +complete -c cheat -s e -f -x --description "Edit " complete -c cheat -s l -l list -f -x --description "List all available cheatsheets" complete -c cheat -s d -l cheat-directories -f -x --description "List all current cheat dirs" complete -c cheat --authoritative -f for cheatsheet in (cheat -l | cut -d' ' -f1) complete -c cheat -a "$cheatsheet" + complete -c cheat -o e -a "$cheatsheet" + complete -c cheat -o '-edit' -a "$cheatsheet" end From b1212052f7c6ebef10c1773035f6adb8aa06b1aa Mon Sep 17 00:00:00 2001 From: 0rax Date: Wed, 6 Nov 2013 00:36:49 +0100 Subject: [PATCH 05/10] Added search function into cheat, used a grep like output, if needed it could be changed, discussion is open inside #128 issue --- cheat | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/cheat b/cheat index 120b764..02fd7e0 100755 --- a/cheat +++ b/cheat @@ -65,6 +65,7 @@ class CheatSheets(object): dirs = None sheets = None + def __init__(self): self.dirs = self.__cheat_directories() # verify that we have at least one cheat directory @@ -178,6 +179,13 @@ class CheatSheets(object): return ('\n'.join(sorted(['%s [%s]' % (key.ljust(max_command), value) for key, value in self.sheets.items()]))) + def search(self, term): + for sheet, sheet_dir in self.sheets.iteritems(): + path = os.path.join(sheet_dir, sheet) + with open(path) as f: + for line in f.readlines(): + if term in line: + print sheet + ':', line, # Custom action for argparse class ListDirectories(argparse.Action): @@ -201,6 +209,13 @@ class EditSheet(argparse.Action): 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(): global sheets @@ -239,6 +254,9 @@ def main(): 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') From d85fab763bc3bb7867db436eacf229d5b9894c18 Mon Sep 17 00:00:00 2001 From: 0rax Date: Thu, 7 Nov 2013 00:23:54 +0100 Subject: [PATCH 06/10] Following #128 talk about how output should be made for search. Just changed the way on how the search function output the result, no more ala grep output. Cheatsheets are now parsed [by CheatSheets.__parse_cheat_command_block(self, cheat_fp)] into block (separated by newline), i have seen that all block in cheatsheets are delimited by a blank line, so instead of parsing from first consecutive # to last consecutive command, an output that is not used by all sheets (reference to "7z" cheatfile). And so the block are parsed by begin of the document to blanck line to end of the document. Finally the output is made by indenting the block content by 4 spaces + the title of the sheet on the top. This is a way to handle subcommands in my mind (search "git commit" and you now have all what you want) --- cheat | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/cheat b/cheat index 02fd7e0..fa19657 100755 --- a/cheat +++ b/cheat @@ -179,13 +179,29 @@ class CheatSheets(object): 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_fp): + block = "" + for line in cheat_fp.readlines(): + if line == '\n' or line == '': + yield block + block = "" + else: + block += line + def search(self, term): for sheet, sheet_dir in self.sheets.iteritems(): path = os.path.join(sheet_dir, sheet) with open(path) as f: - for line in f.readlines(): - if term in line: - print sheet + ':', line, + output = '' + for block in self.__parse_cheat_command_block(f): + if term in block: + if not output: + output = sheet + ":\n" + output += ''.join([" " + line + '\n' for line + in block.split('\n')]) + if output: + print output, + # Custom action for argparse class ListDirectories(argparse.Action): From 69428a72798276fe0b6624187e34a4d847166291 Mon Sep 17 00:00:00 2001 From: 0rax Date: Thu, 7 Nov 2013 00:34:37 +0100 Subject: [PATCH 07/10] Some minor modification (open inside the generator for better readability) + one bug fixe (the last block wasnt yiel by the generator) --- cheat | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/cheat b/cheat index fa19657..0e66ebc 100755 --- a/cheat +++ b/cheat @@ -179,26 +179,29 @@ class CheatSheets(object): 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_fp): + def __parse_cheat_command_block(self, cheat): + """Parse text blocks inside specified sheet file""" block = "" - for line in cheat_fp.readlines(): - if line == '\n' or line == '': - yield block - block = "" - else: - block += line + path = os.path.join(self.sheets[cheat], cheat) + with open(path) as fp: + for line in fp.readlines(): + if line == '\n': + yield block + block = "" + else: + block += line + yield block def search(self, term): - for sheet, sheet_dir in self.sheets.iteritems(): - path = os.path.join(sheet_dir, sheet) - with open(path) as f: - output = '' - for block in self.__parse_cheat_command_block(f): - if term in block: - if not output: - output = sheet + ":\n" - output += ''.join([" " + line + '\n' for line - in block.split('\n')]) + """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: print output, From 4c5bd8efa592e8778f9fa223453d59f54b137c0e Mon Sep 17 00:00:00 2001 From: 0rax Date: Thu, 7 Nov 2013 00:41:36 +0100 Subject: [PATCH 08/10] indent block fail :x, no more triple output --- cheat | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cheat b/cheat index 0e66ebc..b87179e 100755 --- a/cheat +++ b/cheat @@ -195,6 +195,7 @@ class CheatSheets(object): def search(self, term): """Search for a term in sheetcheats""" for cheat in self.sheets.keys(): + print cheat output = '' for block in self.__parse_cheat_command_block(cheat): if term in block: @@ -202,8 +203,8 @@ class CheatSheets(object): output = cheat + ":\n" output += ''.join([" " + line + '\n' for line in block.split('\n')]) - if output: - print output, + if output: + print output, # Custom action for argparse From b264c19491aae3988de3a2047a7784535f36d28f Mon Sep 17 00:00:00 2001 From: 0rax Date: Thu, 7 Nov 2013 00:42:37 +0100 Subject: [PATCH 09/10] remove debug --- cheat | 1 - 1 file changed, 1 deletion(-) diff --git a/cheat b/cheat index b87179e..e9924ba 100755 --- a/cheat +++ b/cheat @@ -195,7 +195,6 @@ class CheatSheets(object): def search(self, term): """Search for a term in sheetcheats""" for cheat in self.sheets.keys(): - print cheat output = '' for block in self.__parse_cheat_command_block(cheat): if term in block: From fc2bb05f7bc54e0ffb12da209fd36ec31a7867ff Mon Sep 17 00:00:00 2001 From: 0rax Date: Thu, 7 Nov 2013 11:23:51 +0100 Subject: [PATCH 10/10] change block generator not to held block is they are empty + minor pep8 refacto (pylint:8.85/10) --- cheat | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/cheat b/cheat index e9924ba..58be41b 100755 --- a/cheat +++ b/cheat @@ -61,11 +61,10 @@ def pretty_print(filename): 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 @@ -155,22 +154,24 @@ class CheatSheets(object): # 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)]) + 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)]) + subprocess.call(editor + + [os.path.join(cs.cheat_dir, cheat)]) else: - print >> sys.stderr, ("Couldn't create '%s' cheatsheet.\n" - "Please retry usig sudo." - % cheat) + error_msg = ("Couldn't create '%s' cheatsheet.\n" + "Please retry usig sudo." % cheat) + print >> sys.stderr, error_msg exit(1) - except OSError, e: + except OSError, errno: print >> sys.stderr, ("Could not launch `%s` as your editor : %s" - % (editor[0], e.strerror)) + % (editor[0], errno.strerror)) exit(1) def list(self): @@ -183,14 +184,16 @@ class CheatSheets(object): """Parse text blocks inside specified sheet file""" block = "" path = os.path.join(self.sheets[cheat], cheat) - with open(path) as fp: - for line in fp.readlines(): + with open(path) as cheat_fp: + for line in cheat_fp.readlines(): if line == '\n': - yield block + if block: + yield block block = "" else: block += line - yield block + if block: + yield block def search(self, term): """Search for a term in sheetcheats""" @@ -236,7 +239,7 @@ class SearchSheet(argparse.Action): def main(): - + """Main execution function""" global sheets sheets = CheatSheets() @@ -294,8 +297,8 @@ def main(): pretty_print(filename) else: with open(filename) as istream: - for l in istream: - sys.stdout.write(l) + for line in istream: + sys.stdout.write(line) # if it does not, say so else: