No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

shibboleth.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. import cmd
  2. import glob
  3. import logging
  4. import os
  5. import re
  6. import readline
  7. import sys
  8. import pkg_resources
  9. from datetime import datetime
  10. from textwrap import dedent
  11. logger = logging.getLogger(__name__)
  12. __version__ = '0.1.11'
  13. DEFAULT_COLORS = {
  14. '1-now': 31, #red
  15. '2-next': 34, #blue
  16. '3-soon': 92, #light green
  17. '4-later': 32, #green
  18. '5-someday': 90, #dark gray
  19. '6-waiting': 95, # light pink?
  20. }
  21. TAG_PATTERN = r'\[(.*)\]'
  22. PRIORITIES = {
  23. '1':'1-now',
  24. '2':'2-next',
  25. '3':'3-soon',
  26. '4':'4-later',
  27. '5':'5-someday',
  28. '6':'6-waiting',
  29. }
  30. class Filename:
  31. def __init__(self, filename):
  32. self.tags = []
  33. tags_found = re.search(TAG_PATTERN, filename)
  34. if tags_found:
  35. self.tags.extend(tags_found.groups()[0].split())
  36. if '1-now' in self.tags:
  37. self._priority = '1-now'
  38. elif '2-next' in self.tags:
  39. self._priority = '2-next'
  40. elif '3-soon' in self.tags:
  41. self._priority = '3-soon'
  42. elif '4-later' in self.tags:
  43. self._priority = '4-later'
  44. elif '5-someday' in self.tags:
  45. self._priority = '5-someday'
  46. elif '6-waiting' in self.tags:
  47. self._priority = '6-waiting'
  48. else:
  49. self._priority = None
  50. self.filename = filename
  51. @property
  52. def colorized(self):
  53. filename = self.filename
  54. for tag in self.tags or []:
  55. color = DEFAULT_COLORS.get(tag, '32')
  56. colorized = f'\x1b[{color}m{tag}\x1b[0m'
  57. filename = filename.replace(tag, colorized)
  58. return filename
  59. @property
  60. def priority(self):
  61. return self._priority
  62. @priority.setter
  63. def priority(self, value):
  64. if self._priority:
  65. head, _, tail = self.filename.rpartition(self._priority)
  66. filename = f'{head}{value}{tail}'
  67. self.tags.remove(self._priority)
  68. else:
  69. head, _, tail = self.filename.rpartition(']')
  70. filename = f'{head} {value}]{tail}'
  71. self._priority = value
  72. self.tags.append(value)
  73. os.rename(self.filename, filename)
  74. self.filename = filename
  75. class Shibboleth(cmd.Cmd):
  76. def __init__(self):
  77. super().__init__()
  78. self.prompt = f'\N{RIGHTWARDS HARPOON WITH BARB UPWARDS}\x1b[34mshibboleth\x1b[0m:{os.getcwd()}\n>'
  79. self.selected = None
  80. readline.set_completion_display_matches_hook(self.display_completion)
  81. readline.set_completer_delims(
  82. readline.get_completer_delims().replace('-', '')
  83. )
  84. self.editor = os.environ.get('EDITOR', 'vim')
  85. self.intro = dedent(f'''
  86. Welcome to Shibboleth {__version__}, the tool designed to be *your*
  87. secret weapon.
  88. Your editor is currently {self.editor}. If you don't like that, you
  89. should change or set your EDITOR environment variable.
  90. ''')
  91. def display_completion(self, substitution, matches, longest_match_length):
  92. logger.debug('>>display_completion')
  93. print()
  94. print(' '.join(matches))
  95. print(self.prompt, end='')
  96. print(readline.get_line_buffer(), end='')
  97. sys.stdout.flush()
  98. def complete(self, *args, **kwargs):
  99. logger.debug('>>complete')
  100. logger.debug('%r %r', args, kwargs)
  101. cmd = readline.get_line_buffer().split(None, maxsplit=1)[0]
  102. if cmd in ('sel', 'select', 'e', 'edit'):
  103. pass
  104. #else:
  105. res = super().complete(*args, **kwargs)
  106. logger.debug('Result: %r', res)
  107. return res
  108. def cmdloop(self):
  109. logger.debug('>>cmdloop')
  110. while True:
  111. try:
  112. super().cmdloop()
  113. break
  114. except KeyboardInterrupt:
  115. print()
  116. print('^C caught - use `q` to quit')
  117. def postcmd(self, stop, line):
  118. logger.debug('>>postcmd')
  119. self.prompt = f'\N{RIGHTWARDS HARPOON WITH BARB UPWARDS}\x1b[34mshibboleth\x1b[0m:{os.getcwd()}\n>'
  120. if self.selected:
  121. self.prompt = f'\N{RIGHTWARDS HARPOON WITH BARB UPWARDS}\x1b[34mshibboleth\x1b[0m:{self.selected.colorized}\n>'
  122. return stop
  123. def do_cd(self, line):
  124. logger.debug('>>do_cd')
  125. try:
  126. os.chdir(line)
  127. except Exception as e:
  128. print(e)
  129. def complete_cd(self, text, line, begidx, endidx):
  130. logger.debug('>>complete_cd')
  131. paths = glob.glob(text+'*')
  132. return paths
  133. def do_pls(self, line):
  134. logger.debug('>>do_pls')
  135. '''
  136. Priority list - list files in the folder that have the
  137. specified priority.
  138. '''
  139. files = [Filename(name) for name in os.listdir(os.path.curdir)]
  140. targets = {
  141. '1': '1-now',
  142. '2': '2-next',
  143. '3': '3-soon',
  144. '4': '4-later',
  145. '5': '5-someday',
  146. '6': '6-waiting',
  147. }
  148. if line:
  149. try:
  150. target = targets[line]
  151. except KeyError:
  152. print(f'Unknown priority {line!r}')
  153. target = None
  154. for file_ in files:
  155. if not line or line == '1':
  156. if '1-now' in file_.tags:
  157. print(file_.colorized)
  158. elif target:
  159. if target in file_.tags:
  160. print(file_.colorized)
  161. def do_now(self, line):
  162. logger.debug('>>do_now')
  163. self.do_pls(line='1')
  164. def do_next(self, line):
  165. logger.debug('>>do_next')
  166. self.do_pls(line='2')
  167. def do_soon(self, line):
  168. logger.debug('>>do_soon')
  169. self.do_pls(line='3')
  170. def do_later(self, line):
  171. logger.debug('>>do_later')
  172. self.do_pls(line='4')
  173. def do_someday(self, line):
  174. logger.debug('>>do_someday')
  175. self.do_pls(line='5')
  176. def do_waiting(self, line):
  177. logger.debug('>>do_waiting')
  178. self.do_pls(line='6')
  179. def do_deselect(self, line):
  180. logger.debug('>>do_deselect')
  181. self.selected = None
  182. def do_select(self, line):
  183. logger.debug('>>do_select')
  184. if not line:
  185. self.do_deselect()
  186. else:
  187. if not os.path.isfile(line):
  188. print(f'Unknown file {line!r}')
  189. else:
  190. self.selected = Filename(os.path.abspath(line))
  191. def complete_select(self, text, line, begidx, endidx):
  192. logger.debug('>>complete_select')
  193. paths = glob.glob(text+'*')
  194. logger.debug('Possible paths: %r', paths)
  195. return paths
  196. def complete_edit(self, text, line, begidx, endidx):
  197. logger.debug('>>complete_edit')
  198. return complete_select
  199. def do_priority(self, line):
  200. logger.debug('>>do_priority')
  201. if not self.selected:
  202. print('Select a file first and try again')
  203. else:
  204. try:
  205. self.selected.priority = PRIORITIES[line]
  206. except KeyError:
  207. print(f'Unknown priority {line!r}')
  208. def do_ls(self, line):
  209. logger.debug('>>do_ls')
  210. line = os.path.expanduser(line)
  211. if not line:
  212. files = os.listdir(os.path.curdir)
  213. elif os.path.isdir(line):
  214. files = os.listdir(line)
  215. else:
  216. files = glob.glob(line)
  217. for filename in files:
  218. print(Filename(filename).colorized)
  219. def do_show(self, line):
  220. logger.debug('>>do_show')
  221. if not self.selected or line:
  222. print('Select a file and try again')
  223. else:
  224. filename = self.selected.filename if self.selected else line
  225. print('*'*80)
  226. with open(filename) as f:
  227. print(f.read())
  228. print('*'*80)
  229. def do_edit(self, line):
  230. logger.debug('>>do_edit')
  231. if not (self.selected or line):
  232. print('Select a file and try again')
  233. else:
  234. filename = self.selected.filename if self.selected else line
  235. flags = ''
  236. if self.editor in ('vi', 'vim'):
  237. flags = '-n'
  238. os.system(f'{self.editor} {flags} "{filename}"')
  239. def do_complete(self, line):
  240. logger.debug('>>do_complete')
  241. if not self.selected or line:
  242. print('Select a file and try again')
  243. else:
  244. filename = self.selected.filename if self.selected else line
  245. os.rename(
  246. filename,
  247. os.path.join(
  248. os.path.dirname(filename),
  249. 'completed',
  250. os.path.basename(filename),
  251. ),
  252. )
  253. self.selected = None
  254. def do_new(self, line):
  255. logger.debug('>>do_new')
  256. if line:
  257. title = line.replace(' ', '-')
  258. else:
  259. title = input('Title: ').strip().replace(' ', '-')
  260. filename = f'{title}[{datetime.now():%Y%m%d~%H%M%S}].md'
  261. self.selected = None
  262. self.do_edit(filename)
  263. self.do_select(filename)
  264. def do_exit(self, line):
  265. logger.debug('>>do_exit')
  266. print('Goodbye!')
  267. return True
  268. def do_EOF(self, line):
  269. logger.debug('>>do_EOF')
  270. print()
  271. return self.do_exit(line)
  272. def do__debug(self, line):
  273. logger.debug('>>do__debug')
  274. import pdb; pdb.set_trace()
  275. def do_log(self, line):
  276. logger.debug('>>do_log')
  277. action, *rest = line.split(None, maxsplit=1)
  278. if action == 'off':
  279. logger.info('Turning logging off')
  280. logger.handlers.clear()
  281. elif action == 'on':
  282. level = getattr(logging, ''.join(rest).upper() or 'DEBUG')
  283. logger.handlers.clear()
  284. h = logging.FileHandler(
  285. 'shibboleth.log'
  286. )
  287. h.setLevel(level)
  288. logger.setLevel(level)
  289. logger.addHandler(h)
  290. logger.info('Logging turned on, level - %r', level)
  291. # Aliases
  292. do_p = do_priority
  293. do_e = do_edit
  294. do_sel = do_select
  295. do_quit = do_exit
  296. do_q = do_exit
  297. do_done = do_complete
  298. do_stop = do_deselect
  299. complete_sel = complete_select
  300. complete_e = complete_edit
  301. def colorfy(filename):
  302. logger.debug('>>colorfy')
  303. tags_found = re.search(tag_pattern, filename)
  304. tags = None
  305. if tags_found:
  306. tags = tags_found.groups()[0].split()
  307. for tag in tags:
  308. color = DEFAULT_COLORS.get(tag, '32')
  309. colorized = f'\x1b[{color}m{tag}\x1b[0m'
  310. filename = filename.replace(tag, colorized)
  311. return filename, tags
  312. def run():
  313. # I'll never ever write a song about the shibby
  314. shibby = Shibboleth()
  315. if sys.argv[1:]:
  316. shibby.onecmd(' '.join(sys.argv[1:]))
  317. else:
  318. shibby.cmdloop()
  319. if __name__ == '__main__':
  320. run()