tools.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. #============================================================================
  2. # This file is part of Pwman3.
  3. #
  4. # Pwman3 is free software; you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License, version 2
  6. # as published by the Free Software Foundation;
  7. #
  8. # Pwman3 is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with Pwman3; if not, write to the Free Software
  15. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  16. #============================================================================
  17. # Copyright (C) 2013 Oz Nahum <nahumoz@gmail.com>
  18. #============================================================================
  19. """
  20. Define the CLI interface for pwman3 and the helper functions
  21. """
  22. from pwman.util.callback import Callback
  23. import pwman.util.config as config
  24. import subprocess as sp
  25. import getpass
  26. import sys
  27. import struct
  28. import os
  29. import colorama
  30. from pwman.data.tags import TagNew as Tag
  31. if sys.platform != 'win32':
  32. import termios
  33. import fcntl
  34. import tty
  35. try:
  36. import pyreadline as readline
  37. _readline_available = True
  38. except ImportError:
  39. _readline_available = False
  40. # raise ImportError("You need 'pyreadline' on Windows")
  41. else:
  42. try:
  43. import readline
  44. _readline_available = True
  45. except ImportError, e:
  46. _readline_available = False
  47. _defaultwidth = 10
  48. class ANSI(object):
  49. """
  50. ANSI Colors
  51. """
  52. Reset = 0
  53. Bold = 1
  54. Underscore = 2
  55. Black = 30
  56. Red = 31
  57. Green = 32
  58. Yellow = 33
  59. Blue = 34
  60. Magenta = 35
  61. Cyan = 36
  62. White = 37
  63. def typeset(text, color, bold=False, underline=False):
  64. """
  65. print colored strings using colorama
  66. """
  67. if not config.get_value("Global", "colors") == 'yes':
  68. return text
  69. if bold:
  70. text = colorama.Style.BRIGHT + text
  71. if underline and not 'win32' in sys.platform:
  72. text = ANSI.Underscore + text
  73. return color + text + colorama.Style.RESET_ALL
  74. def select(question, possible):
  75. """
  76. select input from user
  77. """
  78. for i in range(0, len(possible)):
  79. print ("%d - %-" + str(_defaultwidth) + "s") % (i + 1, possible[i])
  80. while 1:
  81. uinput = getonechar(question)
  82. if uinput.isdigit() and int(uinput) in range(1, len(possible) + 1):
  83. return possible[int(uinput) - 1]
  84. def text_to_clipboards(text):
  85. """
  86. copy text to clipboard
  87. credit:
  88. https://pythonadventures.wordpress.com/tag/xclip/
  89. """
  90. # "primary":
  91. try:
  92. xsel_proc = sp.Popen(['xsel', '-pi'], stdin=sp.PIPE)
  93. xsel_proc.communicate(text)
  94. # "clipboard":
  95. xsel_proc = sp.Popen(['xsel', '-bi'], stdin=sp.PIPE)
  96. xsel_proc.communicate(text)
  97. except OSError, e:
  98. print e, "\nExecuting xsel failed, is it installed ?\n \
  99. please check your configuration file ... "
  100. def text_to_mcclipboard(text):
  101. """
  102. copy text to mac os x clip board
  103. credit:
  104. https://pythonadventures.wordpress.com/tag/xclip/
  105. """
  106. # "primary":
  107. try:
  108. pbcopy_proc = sp.Popen(['pbcopy'], stdin=sp.PIPE)
  109. pbcopy_proc.communicate(text)
  110. except OSError, e:
  111. print e, "\nExecuting pbcoy failed..."
  112. def open_url(link, macosx=False):
  113. """
  114. launch xdg-open or open in MacOSX with url
  115. """
  116. uopen = "xdg-open"
  117. if macosx:
  118. uopen = "open"
  119. try:
  120. sp.Popen([uopen, link], stdin=sp.PIPE)
  121. except OSError, e:
  122. print "Executing open_url failed with:\n", e
  123. def getpassword(question, width=_defaultwidth, echo=False,
  124. reader=getpass.getpass):
  125. if echo:
  126. print question.ljust(width),
  127. return sys.stdin.readline().rstrip()
  128. else:
  129. while True:
  130. a1 = reader(question.ljust(width))
  131. if not a1:
  132. return a1
  133. a2 = reader("[Repeat] %s" % (question.ljust(width)))
  134. if a1 == a2:
  135. return a1
  136. else:
  137. print "Passwords don't match. Try again."
  138. def gettermsize():
  139. s = struct.pack("HHHH", 0, 0, 0, 0)
  140. f = sys.stdout.fileno()
  141. x = fcntl.ioctl(f, termios.TIOCGWINSZ, s)
  142. rows, cols, width, height = struct.unpack("HHHH", x)
  143. return rows, cols
  144. def getinput(question, default="", reader=raw_input,
  145. completer=None, width=_defaultwidth):
  146. """
  147. http://stackoverflow.com/questions/2617057/\
  148. supply-inputs-to-python-unittests
  149. """
  150. if reader == raw_input:
  151. if not _readline_available:
  152. return raw_input(question.ljust(width))
  153. else:
  154. def defaulter():
  155. """define default behavior startup"""
  156. if _readline_available:
  157. readline.insert_text(default)
  158. readline.set_startup_hook(defaulter)
  159. oldcompleter = readline.get_completer()
  160. readline.set_completer(completer)
  161. x = raw_input(question.ljust(width))
  162. readline.set_completer(completer)
  163. readline.set_startup_hook()
  164. return x
  165. else:
  166. return reader()
  167. def getyesno(question, defaultyes=False, width=_defaultwidth):
  168. if (defaultyes):
  169. default = "[Y/n]"
  170. else:
  171. default = "[y/N]"
  172. ch = getonechar("%s %s" % (question, default), width)
  173. if (ch == '\n'):
  174. if (defaultyes):
  175. return True
  176. else:
  177. return False
  178. elif (ch == 'y' or ch == 'Y'):
  179. return True
  180. elif (ch == 'n' or ch == 'N'):
  181. return False
  182. else:
  183. return getyesno(question, defaultyes, width)
  184. class CliMenu(object):
  185. def __init__(self):
  186. self.items = []
  187. def add(self, item):
  188. if (isinstance(item, CliMenuItem)):
  189. self.items.append(item)
  190. else:
  191. print item.__class__
  192. def run(self):
  193. while True:
  194. i = 0
  195. for x in self.items:
  196. i = i + 1
  197. # don't break compatability with old db
  198. try:
  199. current = x.getter()
  200. except TypeError:
  201. current = x.getter
  202. currentstr = ''
  203. if type(current) == list:
  204. for c in current:
  205. currentstr += ("%s " % (c))
  206. else:
  207. currentstr = current
  208. print ("%d - %-" + str(_defaultwidth)
  209. + "s %s") % (i, x.name + ":",
  210. currentstr)
  211. print "%c - Finish editing" % ('X')
  212. option = getonechar("Enter your choice:")
  213. try:
  214. print "selection, ", option
  215. # substract 1 because array subscripts start at 0
  216. selection = int(option) - 1
  217. # new value is created by calling the editor with the
  218. # previous value as a parameter
  219. # TODO: enable overriding password policy as if new node
  220. # is created.
  221. if selection == 1: # for password
  222. value = self.items[selection].editor(0)
  223. else:
  224. try:
  225. edit = self.items[selection].getter()
  226. value = self.items[selection].editor(edit)
  227. self.items[selection].setter(value)
  228. except TypeError:
  229. edit = self.items[selection].getter
  230. value = self.items[selection].editor(edit)
  231. self.items[selection].setter = value
  232. except (ValueError, IndexError):
  233. if (option.upper() == 'X'):
  234. break
  235. print "Invalid selection"
  236. def runner(self, new_node):
  237. while True:
  238. i = 0
  239. for x in self.items:
  240. i = i + 1
  241. # don't break compatability with old db
  242. try:
  243. current = x.getter()
  244. except TypeError:
  245. current = x.getter
  246. except AttributeError:
  247. current = x
  248. currentstr = ''
  249. if type(current) == list:
  250. for c in current:
  251. try:
  252. currentstr += ' ' + c
  253. except TypeError:
  254. currentstr += ' ' + c._name
  255. else:
  256. currentstr = current
  257. print ("%d - %-" + str(_defaultwidth)
  258. + "s %s") % (i, x.name + ":",
  259. currentstr)
  260. print "%c - Finish editing" % ('X')
  261. option = getonechar("Enter your choice:")
  262. try:
  263. print "selection, ", option
  264. # substract 1 because array subscripts start at 0
  265. selection = int(option) - 1
  266. # new value is created by calling the editor with the
  267. # previous value as a parameter
  268. # TODO: enable overriding password policy as if new node
  269. # is created.
  270. if selection == 0:
  271. new_node.username = getinput("Username:")
  272. self.items[0].getter = new_node.username
  273. self.items[0].setter = new_node.username
  274. elif selection == 1: # for password
  275. value = self.items[selection].editor(0)
  276. new_node.password = value
  277. self.items[1].getter = new_node.password
  278. self.items[1].setter = new_node.password
  279. elif selection == 2:
  280. new_node.url = getinput("Url:")
  281. self.items[2].getter = new_node.url
  282. self.items[2].setter = new_node.url
  283. elif selection == 3: # for notes
  284. new_node.notes = getinput("Notes:")
  285. self.items[3].getter = new_node.notes
  286. self.items[3].setter = new_node.notes
  287. elif selection == 4:
  288. taglist = getinput("Tags:")
  289. tagstrings = taglist.split()
  290. tags = [Tag(tn) for tn in tagstrings]
  291. new_node.tags = tags
  292. self.items[4].setter = new_node.tags
  293. self.items[4].getter = new_node.tags
  294. except (ValueError, IndexError):
  295. if (option.upper() == 'X'):
  296. break
  297. print "Invalid selection"
  298. def getonechar(question, width=_defaultwidth):
  299. question = "%s " % (question)
  300. print question.ljust(width),
  301. sys.stdout.flush()
  302. fd = sys.stdin.fileno()
  303. # tty module exists only if we are on Posix
  304. try:
  305. tty_mode = tty.tcgetattr(fd)
  306. tty.setcbreak(fd)
  307. except NameError:
  308. pass
  309. try:
  310. ch = os.read(fd, 1)
  311. finally:
  312. try:
  313. tty.tcsetattr(fd, tty.TCSAFLUSH, tty_mode)
  314. except NameError:
  315. pass
  316. print ch
  317. return ch
  318. class CliMenuItem(object):
  319. def __init__(self, name, editor, getter, setter):
  320. self.name = name
  321. self.editor = editor
  322. self.getter = getter
  323. self.setter = setter
  324. class CLICallback(Callback):
  325. def getinput(self, question):
  326. return raw_input(question)
  327. def getsecret(self, question):
  328. return getpass.getpass(question + ":")