import cmd import glob import logging import os import re import readline import sys import pkg_resources from datetime import datetime from textwrap import dedent logger = logging.getLogger(__name__) __version__ = '0.1.11' DEFAULT_COLORS = { '1-now': 31, #red '2-next': 34, #blue '3-soon': 92, #light green '4-later': 32, #green '5-someday': 90, #dark gray '6-waiting': 95, # light pink? } TAG_PATTERN = r'\[(.*)\]' PRIORITIES = { '1':'1-now', '2':'2-next', '3':'3-soon', '4':'4-later', '5':'5-someday', '6':'6-waiting', } class Filename: def __init__(self, filename): self.tags = [] tags_found = re.search(TAG_PATTERN, filename) if tags_found: self.tags.extend(tags_found.groups()[0].split()) if '1-now' in self.tags: self._priority = '1-now' elif '2-next' in self.tags: self._priority = '2-next' elif '3-soon' in self.tags: self._priority = '3-soon' elif '4-later' in self.tags: self._priority = '4-later' elif '5-someday' in self.tags: self._priority = '5-someday' elif '6-waiting' in self.tags: self._priority = '6-waiting' else: self._priority = None self.filename = filename @property def colorized(self): filename = self.filename for tag in self.tags or []: color = DEFAULT_COLORS.get(tag, '32') colorized = f'\x1b[{color}m{tag}\x1b[0m' filename = filename.replace(tag, colorized) return filename @property def priority(self): return self._priority @priority.setter def priority(self, value): if self._priority: head, _, tail = self.filename.rpartition(self._priority) filename = f'{head}{value}{tail}' self.tags.remove(self._priority) else: head, _, tail = self.filename.rpartition(']') filename = f'{head} {value}]{tail}' self._priority = value self.tags.append(value) os.rename(self.filename, filename) self.filename = filename class Shibboleth(cmd.Cmd): def __init__(self): super().__init__() self.prompt = f'\N{RIGHTWARDS HARPOON WITH BARB UPWARDS}\x1b[34mshibboleth\x1b[0m:{os.getcwd()}\n>' self.selected = None readline.set_completion_display_matches_hook(self.display_completion) readline.set_completer_delims( readline.get_completer_delims().replace('-', '') ) self.editor = os.environ.get('EDITOR', 'vim') self.intro = dedent(f''' Welcome to Shibboleth {__version__}, the tool designed to be *your* secret weapon. Your editor is currently {self.editor}. If you don't like that, you should change or set your EDITOR environment variable. ''') def display_completion(self, substitution, matches, longest_match_length): logger.debug('>>display_completion') print() print(' '.join(matches)) print(self.prompt, end='') print(readline.get_line_buffer(), end='') sys.stdout.flush() def complete(self, *args, **kwargs): logger.debug('>>complete') logger.debug('%r %r', args, kwargs) cmd = readline.get_line_buffer().split(None, maxsplit=1)[0] if cmd in ('sel', 'select', 'e', 'edit'): pass #else: res = super().complete(*args, **kwargs) logger.debug('Result: %r', res) return res def cmdloop(self): logger.debug('>>cmdloop') while True: try: super().cmdloop() break except KeyboardInterrupt: print() print('^C caught - use `q` to quit') def postcmd(self, stop, line): logger.debug('>>postcmd') self.prompt = f'\N{RIGHTWARDS HARPOON WITH BARB UPWARDS}\x1b[34mshibboleth\x1b[0m:{os.getcwd()}\n>' if self.selected: self.prompt = f'\N{RIGHTWARDS HARPOON WITH BARB UPWARDS}\x1b[34mshibboleth\x1b[0m:{self.selected.colorized}\n>' return stop def do_cd(self, line): logger.debug('>>do_cd') try: os.chdir(line) except Exception as e: print(e) def complete_cd(self, text, line, begidx, endidx): logger.debug('>>complete_cd') paths = glob.glob(text+'*') return paths def do_pls(self, line): logger.debug('>>do_pls') ''' Priority list - list files in the folder that have the specified priority. ''' files = [Filename(name) for name in os.listdir(os.path.curdir)] targets = { '1': '1-now', '2': '2-next', '3': '3-soon', '4': '4-later', '5': '5-someday', '6': '6-waiting', } if line: try: target = targets[line] except KeyError: print(f'Unknown priority {line!r}') target = None for file_ in files: if not line or line == '1': if '1-now' in file_.tags: print(file_.colorized) elif target: if target in file_.tags: print(file_.colorized) def do_now(self, line): logger.debug('>>do_now') self.do_pls(line='1') def do_next(self, line): logger.debug('>>do_next') self.do_pls(line='2') def do_soon(self, line): logger.debug('>>do_soon') self.do_pls(line='3') def do_later(self, line): logger.debug('>>do_later') self.do_pls(line='4') def do_someday(self, line): logger.debug('>>do_someday') self.do_pls(line='5') def do_waiting(self, line): logger.debug('>>do_waiting') self.do_pls(line='6') def do_deselect(self, line): logger.debug('>>do_deselect') self.selected = None def do_select(self, line): logger.debug('>>do_select') if not line: self.do_deselect() else: if not os.path.isfile(line): print(f'Unknown file {line!r}') else: self.selected = Filename(os.path.abspath(line)) def complete_select(self, text, line, begidx, endidx): logger.debug('>>complete_select') paths = glob.glob(text+'*') logger.debug('Possible paths: %r', paths) return paths def complete_edit(self, text, line, begidx, endidx): logger.debug('>>complete_edit') return complete_select def do_priority(self, line): logger.debug('>>do_priority') if not self.selected: print('Select a file first and try again') else: try: self.selected.priority = PRIORITIES[line] except KeyError: print(f'Unknown priority {line!r}') def do_ls(self, line): logger.debug('>>do_ls') line = os.path.expanduser(line) if not line: files = os.listdir(os.path.curdir) elif os.path.isdir(line): files = os.listdir(line) else: files = glob.glob(line) for filename in files: print(Filename(filename).colorized) def do_show(self, line): logger.debug('>>do_show') if not self.selected or line: print('Select a file and try again') else: filename = self.selected.filename if self.selected else line print('*'*80) with open(filename) as f: print(f.read()) print('*'*80) def do_edit(self, line): logger.debug('>>do_edit') if not (self.selected or line): print('Select a file and try again') else: filename = self.selected.filename if self.selected else line flags = '' if self.editor in ('vi', 'vim'): flags = '-n' os.system(f'{self.editor} {flags} "{filename}"') def do_complete(self, line): logger.debug('>>do_complete') if not self.selected or line: print('Select a file and try again') else: filename = self.selected.filename if self.selected else line os.rename( filename, os.path.join( os.path.dirname(filename), 'completed', os.path.basename(filename), ), ) self.selected = None def do_new(self, line): logger.debug('>>do_new') if line: title = line.replace(' ', '-') else: title = input('Title: ').strip().replace(' ', '-') filename = f'{title}[{datetime.now():%Y%m%d~%H%M%S}].md' self.selected = None self.do_edit(filename) self.do_select(filename) def do_exit(self, line): logger.debug('>>do_exit') print('Goodbye!') return True def do_EOF(self, line): logger.debug('>>do_EOF') print() return self.do_exit(line) def do__debug(self, line): logger.debug('>>do__debug') import pdb; pdb.set_trace() def do_log(self, line): logger.debug('>>do_log') action, *rest = line.split(None, maxsplit=1) if action == 'off': logger.info('Turning logging off') logger.handlers.clear() elif action == 'on': level = getattr(logging, ''.join(rest).upper() or 'DEBUG') logger.handlers.clear() h = logging.FileHandler( 'shibboleth.log' ) h.setLevel(level) logger.setLevel(level) logger.addHandler(h) logger.info('Logging turned on, level - %r', level) # Aliases do_p = do_priority do_e = do_edit do_sel = do_select do_quit = do_exit do_q = do_exit do_done = do_complete do_stop = do_deselect complete_sel = complete_select complete_e = complete_edit def colorfy(filename): logger.debug('>>colorfy') tags_found = re.search(tag_pattern, filename) tags = None if tags_found: tags = tags_found.groups()[0].split() for tag in tags: color = DEFAULT_COLORS.get(tag, '32') colorized = f'\x1b[{color}m{tag}\x1b[0m' filename = filename.replace(tag, colorized) return filename, tags def run(): # I'll never ever write a song about the shibby shibby = Shibboleth() if sys.argv[1:]: shibby.onecmd(' '.join(sys.argv[1:])) else: shibby.cmdloop() if __name__ == '__main__': run()