utils.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. import os, sys, re, errno
  2. try:
  3. from json import loads as parse_json, dumps as compile_json
  4. except ImportError:
  5. from simplejson import loads as parse_json, dumps as compile_json
  6. import urllib2 # This should go and instead use do_url_request everywhere
  7. from urls import API_URLS
  8. from txclib.log import logger
  9. from txclib.exceptions import UnknownCommandError
  10. def find_dot_tx(path = os.path.curdir, previous = None):
  11. """
  12. Return the path where .tx folder is found.
  13. The 'path' should be a DIRECTORY.
  14. This process is functioning recursively from the current directory to each
  15. one of the ancestors dirs.
  16. """
  17. path = os.path.abspath(path)
  18. if path == previous:
  19. return None
  20. joined = os.path.join(path, ".tx")
  21. if os.path.isdir(joined):
  22. return path
  23. else:
  24. return find_dot_tx(os.path.dirname(path), path)
  25. #################################################
  26. # Parse file filter expressions and create regex
  27. def regex_from_filefilter(file_filter, root_path = os.path.curdir):
  28. """
  29. Create proper regex from <lang> expression
  30. """
  31. # Force expr to be a valid regex expr (escaped) but keep <lang> intact
  32. expr_re = re.escape(os.path.join(root_path, file_filter))
  33. expr_re = expr_re.replace("\\<lang\\>", '<lang>').replace(
  34. '<lang>', '([^%(sep)s]+)' % { 'sep': re.escape(os.path.sep)})
  35. return "^%s$" % expr_re
  36. TX_URLS = {
  37. 'resource': '(?P<hostname>https?://(\w|\.|:|-)+)/projects/p/(?P<project>(\w|-)+)/resource/(?P<resource>(\w|-)+)/?$',
  38. 'release': '(?P<hostname>https?://(\w|\.|:|-)+)/projects/p/(?P<project>(\w|-)+)/r/(?P<release>(\w|-)+)/?$',
  39. 'project': '(?P<hostname>https?://(\w|\.|:|-)+)/projects/p/(?P<project>(\w|-)+)/?$',
  40. }
  41. def parse_tx_url(url):
  42. """
  43. Try to match given url to any of the valid url patterns specified in
  44. TX_URLS. If not match is found, we raise exception
  45. """
  46. for type in TX_URLS.keys():
  47. pattern = TX_URLS[type]
  48. m = re.match(pattern, url)
  49. if m:
  50. return type, m.groupdict()
  51. raise Exception("tx: Malformed url given. Please refer to our docs: http://bit.ly/txautor")
  52. def get_details(api_call, username, password, *args, **kwargs):
  53. """
  54. Get the tx project info through the API.
  55. This function can also be used to check the existence of a project.
  56. """
  57. import base64
  58. url = (API_URLS[api_call] % (kwargs)).encode('UTF-8')
  59. req = urllib2.Request(url=url)
  60. base64string = base64.encodestring('%s:%s' % (username, password))[:-1]
  61. authheader = "Basic %s" % base64string
  62. req.add_header("Authorization", authheader)
  63. try:
  64. fh = urllib2.urlopen(req)
  65. raw = fh.read()
  66. fh.close()
  67. remote_project = parse_json(raw)
  68. except urllib2.HTTPError, e:
  69. if e.code in [401, 403, 404]:
  70. raise e
  71. else:
  72. # For other requests, we should print the message as well
  73. raise Exception("Remote server replied: %s" % e.read())
  74. except urllib2.URLError, e:
  75. error = e.args[0]
  76. raise Exception("Remote server replied: %s" % error[1])
  77. return remote_project
  78. def valid_slug(slug):
  79. """
  80. Check if a slug contains only valid characters.
  81. Valid chars include [-_\w]
  82. """
  83. try:
  84. a, b = slug.split('.')
  85. except ValueError:
  86. return False
  87. else:
  88. if re.match("^[A-Za-z0-9_-]*$", a) and re.match("^[A-Za-z0-9_-]*$", b):
  89. return True
  90. return False
  91. def discover_commands():
  92. """
  93. Inspect commands.py and find all available commands
  94. """
  95. import inspect
  96. from txclib import commands
  97. command_table = {}
  98. fns = inspect.getmembers(commands, inspect.isfunction)
  99. for name, fn in fns:
  100. if name.startswith("cmd_"):
  101. command_table.update({
  102. name.split("cmd_")[1]:fn
  103. })
  104. return command_table
  105. def exec_command(command, *args, **kwargs):
  106. """
  107. Execute given command
  108. """
  109. commands = discover_commands()
  110. try:
  111. cmd_fn = commands[command]
  112. except KeyError:
  113. raise UnknownCommandError
  114. cmd_fn(*args,**kwargs)
  115. def mkdir_p(path):
  116. try:
  117. if path:
  118. os.makedirs(path)
  119. except OSError, exc: # Python >2.5
  120. if exc.errno == errno.EEXIST:
  121. pass
  122. else:
  123. raise
  124. def confirm(prompt='Continue?', default=True):
  125. """
  126. Prompt the user for a Yes/No answer.
  127. Args:
  128. prompt: The text displayed to the user ([Y/n] will be appended)
  129. default: If the default value will be yes or no
  130. """
  131. valid_yes = ['Y', 'y', 'Yes', 'yes', ]
  132. valid_no = ['N', 'n', 'No', 'no', ]
  133. if default:
  134. prompt = prompt + '[Y/n]'
  135. valid_yes.append('')
  136. else:
  137. prompt = prompt + '[y/N]'
  138. valid_no.append('')
  139. ans = raw_input(prompt)
  140. while (ans not in valid_yes and ans not in valid_no):
  141. ans = raw_input(prompt)
  142. return ans in valid_yes
  143. # Stuff for command line colored output
  144. COLORS = [
  145. 'BLACK', 'RED', 'GREEN', 'YELLOW',
  146. 'BLUE', 'MAGENTA', 'CYAN', 'WHITE'
  147. ]
  148. DISABLE_COLORS = False
  149. def color_text(text, color_name, bold=False):
  150. """
  151. This command can be used to colorify command line output. If the shell
  152. doesn't support this or the --disable-colors options has been set, it just
  153. returns the plain text.
  154. Usage:
  155. print "%s" % color_text("This text is red", "RED")
  156. """
  157. if color_name in COLORS and not DISABLE_COLORS:
  158. return '\033[%s;%sm%s\033[0m' % (
  159. int(bold), COLORS.index(color_name) + 30, text)
  160. else:
  161. return text
  162. ##############################################
  163. # relpath implementation taken from Python 2.7
  164. if not hasattr(os.path, 'relpath'):
  165. if os.path is sys.modules.get('ntpath'):
  166. def relpath(path, start=os.path.curdir):
  167. """Return a relative version of a path"""
  168. if not path:
  169. raise ValueError("no path specified")
  170. start_list = os.path.abspath(start).split(os.path.sep)
  171. path_list = os.path.abspath(path).split(os.path.sep)
  172. if start_list[0].lower() != path_list[0].lower():
  173. unc_path, rest = os.path.splitunc(path)
  174. unc_start, rest = os.path.splitunc(start)
  175. if bool(unc_path) ^ bool(unc_start):
  176. raise ValueError("Cannot mix UNC and non-UNC paths (%s and %s)"
  177. % (path, start))
  178. else:
  179. raise ValueError("path is on drive %s, start on drive %s"
  180. % (path_list[0], start_list[0]))
  181. # Work out how much of the filepath is shared by start and path.
  182. for i in range(min(len(start_list), len(path_list))):
  183. if start_list[i].lower() != path_list[i].lower():
  184. break
  185. else:
  186. i += 1
  187. rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:]
  188. if not rel_list:
  189. return os.path.curdir
  190. return os.path.join(*rel_list)
  191. else:
  192. # default to posixpath definition
  193. def relpath(path, start=os.path.curdir):
  194. """Return a relative version of a path"""
  195. if not path:
  196. raise ValueError("no path specified")
  197. start_list = os.path.abspath(start).split(os.path.sep)
  198. path_list = os.path.abspath(path).split(os.path.sep)
  199. # Work out how much of the filepath is shared by start and path.
  200. i = len(os.path.commonprefix([start_list, path_list]))
  201. rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:]
  202. if not rel_list:
  203. return os.path.curdir
  204. return os.path.join(*rel_list)
  205. else:
  206. from os.path import relpath