tools.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  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 __future__ import print_function
  23. import subprocess as sp
  24. import getpass
  25. import sys
  26. import struct
  27. import shlex
  28. import platform
  29. import colorama
  30. import os
  31. #from pwman.util.config import get_pass_conf
  32. from pwman.util.callback import Callback
  33. if sys.version_info.major > 2: # pragma: no cover
  34. raw_input = input
  35. if sys.platform != 'win32':
  36. import termios
  37. import fcntl
  38. import readline
  39. _readline_available = True
  40. else: # pragma: no cover
  41. try:
  42. import readline
  43. _readline_available = True
  44. except ImportError as e:
  45. _readline_available = False
  46. _defaultwidth = 10
  47. class ANSI(object):
  48. """
  49. ANSI Colors
  50. """
  51. Reset = 0
  52. Bold = 1
  53. Underscore = 2
  54. Black = 30
  55. Red = 31
  56. Green = 32
  57. Yellow = 33
  58. Blue = 34
  59. Magenta = 35
  60. Cyan = 36
  61. White = 37
  62. def typeset(text, color, bold=False, underline=False,
  63. has_colorama=True): # pragma: no cover
  64. """
  65. print colored strings using colorama
  66. """
  67. if not has_colorama:
  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 text_to_clipboards(text): # pragma: no cover
  75. """
  76. copy text to clipboard
  77. credit:
  78. https://pythonadventures.wordpress.com/tag/xclip/
  79. """
  80. # "primary":
  81. try:
  82. xsel_proc = sp.Popen(['xsel', '-pi'], stdin=sp.PIPE)
  83. xsel_proc.communicate(text)
  84. # "clipboard":
  85. xsel_proc = sp.Popen(['xsel', '-bi'], stdin=sp.PIPE)
  86. xsel_proc.communicate(text)
  87. except OSError as e:
  88. print (e, "\nExecuting xsel failed, is it installed ?\n \
  89. please check your configuration file ... ")
  90. def text_to_mcclipboard(text): # pragma: no cover
  91. """
  92. copy text to mac os x clip board
  93. credit:
  94. https://pythonadventures.wordpress.com/tag/xclip/
  95. """
  96. # "primary":
  97. try:
  98. pbcopy_proc = sp.Popen(['pbcopy'], stdin=sp.PIPE)
  99. pbcopy_proc.communicate(text)
  100. except OSError as e:
  101. print (e, "\nExecuting pbcoy failed...")
  102. def open_url(link, macosx=False): # pragma: no cover
  103. """
  104. launch xdg-open or open in MacOSX with url
  105. """
  106. uopen = "xdg-open"
  107. if macosx:
  108. uopen = "open"
  109. try:
  110. sp.Popen([uopen, link], stdin=sp.PIPE)
  111. except OSError as e:
  112. print("Executing open_url failed with:\n", e)
  113. def get_password(question, config):
  114. # TODO: enable old functionallity, with password generator.
  115. if sys.stdin.isatty(): # pragma: no cover
  116. p = getpass.getpass()
  117. else:
  118. p = sys.stdin.readline().rstrip()
  119. return p
  120. def getpassword(question, argsgiven=None,
  121. width=_defaultwidth, echo=False,
  122. reader=getpass.getpass, numerics=False, leetify=False,
  123. symbols=False, special_signs=False,
  124. length=None, config=None): # pragma: no cover
  125. if argsgiven == 1 or length:
  126. while not length:
  127. try:
  128. default_length = config.get_value(
  129. 'Generator', 'default_pw_length') or '8'
  130. length = getinput(
  131. "Password length (default %s): " % default_length,
  132. default=default_length)
  133. length = int(length)
  134. except ValueError:
  135. print("please enter a proper integer")
  136. #password, dumpme = generator.generate_password(
  137. # length, length, True, symbols=leetify, numerics=numerics,
  138. # special_chars=special_signs)
  139. #print ("New password: %s" % (password))
  140. #return password
  141. # no args given
  142. while True:
  143. a1 = reader(question.ljust(width))
  144. if not a1:
  145. return getpassword(
  146. '', argsgiven=1, width=width, echo=echo, reader=reader,
  147. numerics=numerics, leetify=leetify, symbols=symbols,
  148. special_signs=special_signs, length=length, config=config)
  149. a2 = reader("[Repeat] %s" % (question.ljust(width)))
  150. if a1 == a2:
  151. if leetify:
  152. pass # return generator.leetify(a1)
  153. else:
  154. return a1
  155. else:
  156. print ("Passwords don't match. Try again.")
  157. def get_terminal_size():
  158. """ getTerminalSize()
  159. - get width and height of console
  160. - works on linux,os x,windows,cygwin(windows)
  161. originally retrieved from:
  162. http://stackoverflow.com/questions/566746/how-to-get-\
  163. console-window-width-in-python
  164. """
  165. current_os = platform.system()
  166. tuple_xy = None
  167. if current_os == 'Windows':
  168. tuple_xy = _get_terminal_size_windows()
  169. if tuple_xy is None:
  170. tuple_xy = _get_terminal_size_tput()
  171. # needed for window's python in cygwin's xterm!
  172. if current_os in ['Linux', 'Darwin'] or current_os.startswith('CYGWIN'):
  173. tuple_xy = _get_terminal_size_linux()
  174. if tuple_xy is None:
  175. tuple_xy = (80, 25) # default value
  176. return tuple_xy
  177. def _get_terminal_size_windows(): # pragma: no cover
  178. try:
  179. from ctypes import windll, create_string_buffer
  180. # stdin handle is -10
  181. # stdout handle is -11
  182. # stderr handle is -12
  183. h = windll.kernel32.GetStdHandle(-12)
  184. csbi = create_string_buffer(22)
  185. res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
  186. if res:
  187. (bufx, bufy, curx, cury, wattr,
  188. left, top, right, bottom,
  189. maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw)
  190. sizex = right - left + 1
  191. sizey = bottom - top + 1
  192. return sizex, sizey
  193. except:
  194. pass
  195. def _get_terminal_size_tput(): # pragma: no cover
  196. # get terminal width
  197. # src: http://stackoverflow.com/questions/263890/how-do-i-\
  198. # find-the-width-height-of-a-terminal-window
  199. try:
  200. cols = int(sp.check_call(shlex.split('tput cols')))
  201. rows = int(sp.check_call(shlex.split('tput lines')))
  202. return (cols, rows)
  203. except:
  204. pass
  205. def _get_terminal_size_linux(): # pragma: no cover
  206. def ioctl_GWINSZ(fd):
  207. try:
  208. import fcntl
  209. import termios
  210. cr = struct.unpack('hh',
  211. fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
  212. return cr
  213. except:
  214. pass
  215. cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
  216. if not cr:
  217. try:
  218. fd = os.open(os.ctermid(), os.O_RDONLY)
  219. cr = ioctl_GWINSZ(fd)
  220. os.close(fd)
  221. except:
  222. pass
  223. if not cr:
  224. try:
  225. cr = (os.environ['LINES'], os.environ['COLUMNS'])
  226. except:
  227. return None
  228. return int(cr[1]), int(cr[0])
  229. def gettermsize(): # pragma: no cover
  230. if sys.stdout.isatty():
  231. s = struct.pack("HHHH", 0, 0, 0, 0)
  232. f = sys.stdout.fileno()
  233. x = fcntl.ioctl(f, termios.TIOCGWINSZ, s)
  234. rows, cols, width, height = struct.unpack("HHHH", x)
  235. return rows, cols
  236. else:
  237. return 40, 80
  238. def getinput(question, default="", reader=raw_input,
  239. completer=None, width=_defaultwidth): # pragma: no cover
  240. """
  241. http://stackoverflow.com/questions/2617057/\
  242. supply-inputs-to-python-unittests
  243. """
  244. if reader == raw_input:
  245. if not _readline_available:
  246. val = raw_input(question.ljust(width))
  247. if val:
  248. return val
  249. else:
  250. return default
  251. else:
  252. def defaulter():
  253. """define default behavior startup"""
  254. if _readline_available:
  255. readline.insert_text(default)
  256. readline.set_startup_hook(defaulter)
  257. readline.get_completer()
  258. readline.set_completer(completer)
  259. x = raw_input(question.ljust(width))
  260. readline.set_completer(completer)
  261. readline.set_startup_hook()
  262. if not x:
  263. return default
  264. return x
  265. else:
  266. return reader()
  267. class CMDLoop(object): # pragma: no cover
  268. """
  269. The menu that drives editing of a node
  270. """
  271. def __init__(self, config):
  272. self.items = []
  273. self.config = config
  274. def add(self, item):
  275. if (isinstance(item, CliMenuItem)):
  276. self.items.append(item)
  277. else:
  278. print (item.__class__)
  279. def run(self, new_node=None, reader=raw_input):
  280. while True:
  281. for i, x in enumerate(self.items):
  282. current = x.getter
  283. print ("%s - %s: %s" % (i + 1, x.name, current))
  284. print("X - Finish editing")
  285. option = reader("Enter your choice:")[0]
  286. try:
  287. print ("Selection, ", option)
  288. # substract 1 because array subscripts start at 0
  289. selection = int(option) - 1
  290. # new value is created by calling the editor with the
  291. # previous value as a parameter
  292. # TODO: enable overriding password policy as if new node
  293. # is created.
  294. if selection == 0:
  295. new_node.username = getinput("Username:")
  296. self.items[0].getter = new_node.username
  297. self.items[0].setter = new_node.username
  298. elif selection == 1: # for password
  299. new_node.password = get_password("Password:", self.config)
  300. self.items[1].getter = new_node.password
  301. self.items[1].setter = new_node.password
  302. elif selection == 2:
  303. new_node.url = getinput("Url:")
  304. self.items[2].getter = new_node.url
  305. self.items[2].setter = new_node.url
  306. elif selection == 3: # for notes
  307. # new_node.notes = getinput("Notes:")
  308. new_node.notes = reader("Notes:")
  309. self.items[3].getter = new_node.notes
  310. self.items[3].setter = new_node.notes
  311. elif selection == 4:
  312. taglist = getinput("Tags:")
  313. tagstrings = taglist.split()
  314. tags = [tn for tn in tagstrings]
  315. #tags = ''
  316. new_node.tags = tags
  317. self.items[4].setter = new_node.tags
  318. self.items[4].getter = new_node.tags
  319. except (ValueError, IndexError):
  320. if (option.upper() == 'X'):
  321. break
  322. print("Invalid selection")
  323. class CliMenuItem(object): # pragma: no cover
  324. def __init__(self, name, editor, getter, setter):
  325. self.name = name
  326. self.editor = editor
  327. self.getter = getter
  328. self.setter = setter
  329. class CLICallback(Callback): # pragma: no cover
  330. def getinput(self, question):
  331. return raw_input(question)
  332. def getsecret(self, question):
  333. return getpass.getpass(question + ":")
  334. def getnewsecret(self, question):
  335. return getpass.getpass(question + ":")