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.

367 lines
10 KiB

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