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()
|