uscan.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. # vim: set fileencoding=utf-8 :
  2. #
  3. # (C) 2012 Guido Günther <agx@sigxcpu.org>
  4. # This program is free software; you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation; either version 2 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, please see
  16. # <http://www.gnu.org/licenses/>
  17. """Interface to uscan"""
  18. import os, re, subprocess
  19. class UscanError(Exception):
  20. pass
  21. class Uscan(object):
  22. cmd = '/usr/bin/uscan'
  23. def __init__(self, dir='.'):
  24. self._uptodate = False
  25. self._tarball = None
  26. self._dir = os.path.abspath(dir)
  27. @property
  28. def uptodate(self):
  29. return self._uptodate
  30. @property
  31. def tarball(self):
  32. return self._tarball
  33. def _parse(self, out):
  34. r"""
  35. Parse the uscan output return and update the object's properties
  36. @param out: uscan output
  37. @type out: string
  38. >>> u = Uscan('http://example.com/')
  39. >>> u._parse('<target>virt-viewer_0.4.0.orig.tar.gz</target>')
  40. >>> u.tarball
  41. '../virt-viewer_0.4.0.orig.tar.gz'
  42. >>> u.uptodate
  43. False
  44. >>> u._parse('')
  45. Traceback (most recent call last):
  46. ...
  47. UscanError: Couldn't find 'upstream-url' in uscan output
  48. """
  49. source = None
  50. self._uptodate = False
  51. # Check if uscan downloaded something
  52. for row in out.split("\n"):
  53. # uscan >= 2.10.70 has a target element:
  54. m = re.match(r"<target>(.*)</target>", row)
  55. if m:
  56. source = '../%s' % m.group(1)
  57. break
  58. elif row.startswith('<messages>'):
  59. m = re.match(r".*symlinked ([^\s]+) to it", row)
  60. if m:
  61. source = "../%s" % m.group(1)
  62. break
  63. m = re.match(r"Successfully downloaded updated package "
  64. "([^<]+)", row)
  65. if m:
  66. source = "../%s" % m.group(1)
  67. break
  68. # Try to determine the already downloaded sources name
  69. else:
  70. d = {}
  71. try:
  72. for row in out.split("\n"):
  73. for n in ('package',
  74. 'upstream-version',
  75. 'upstream-url'):
  76. m = re.match("<%s>(.*)</%s>" % (n,n), row)
  77. if m:
  78. d[n] = m.group(1)
  79. d["ext"] = os.path.splitext(d['upstream-url'])[1]
  80. # We want the name of the orig tarball if possible
  81. source = ("../%(package)s_%(upstream-version)s."
  82. "orig.tar%(ext)s" % d)
  83. # Fall back to the upstream source name otherwise
  84. if not os.path.exists(source):
  85. source = "../%s" % d['upstream-url'].rsplit('/',1)[1]
  86. if not os.path.exists(source):
  87. raise UscanError("Couldn't find tarball at '%s'" %
  88. source)
  89. except KeyError as e:
  90. raise UscanError("Couldn't find '%s' in uscan output" %
  91. e.args[0])
  92. self._tarball = source
  93. def _parse_uptodate(self, out):
  94. """
  95. Check if the uscan reports that we're up to date.
  96. @param out: uscan output
  97. @type out: string
  98. >>> u = Uscan('http://example.com/')
  99. >>> u._parse_uptodate('<status>up to date</status>')
  100. >>> u.tarball
  101. >>> u.uptodate
  102. True
  103. >>> u._parse_uptodate('')
  104. >>> u.tarball
  105. >>> u.uptodate
  106. False
  107. """
  108. if "<status>up to date</status>" in out:
  109. self._uptodate = True
  110. else:
  111. self._uptodate = False
  112. def _raise_error(self, out):
  113. r"""
  114. Parse the uscan output for errors and warnings and raise
  115. a L{UscanError} exception based on this. If no error detail
  116. is found a generic error message is used.
  117. @param out: uscan output
  118. @type out: string
  119. @raises UscanError: exception raised
  120. >>> u = Uscan('http://example.com/')
  121. >>> u._raise_error("<warnings>uscan warning: "
  122. ... "In watchfile debian/watch, reading webpage\n"
  123. ... "http://a.b/ failed: 500 Cant connect "
  124. ... "to example.com:80 (Bad hostname)</warnings>")
  125. Traceback (most recent call last):
  126. ...
  127. UscanError: Uscan failed: uscan warning: In watchfile debian/watch, reading webpage
  128. http://a.b/ failed: 500 Cant connect to example.com:80 (Bad hostname)
  129. >>> u._raise_error("<errors>uscan: Can't use --verbose if "
  130. ... "you're using --dehs!</errors>")
  131. Traceback (most recent call last):
  132. ...
  133. UscanError: Uscan failed: uscan: Can't use --verbose if you're using --dehs!
  134. >>> u = u._raise_error('')
  135. Traceback (most recent call last):
  136. ...
  137. UscanError: Uscan failed - debug by running 'uscan --verbose'
  138. """
  139. msg = None
  140. for n in ('errors', 'warnings'):
  141. m = re.search("<%s>(.*)</%s>" % (n,n), out, re.DOTALL)
  142. if m:
  143. msg = "Uscan failed: %s" % m.group(1)
  144. break
  145. if not msg:
  146. msg = "Uscan failed - debug by running 'uscan --verbose'"
  147. raise UscanError(msg)
  148. def scan(self, destdir='..'):
  149. """Invoke uscan to fetch a new upstream version"""
  150. p = subprocess.Popen(['uscan', '--symlink', '--destdir=%s' % destdir,
  151. '--dehs'],
  152. cwd=self._dir,
  153. stdout=subprocess.PIPE)
  154. out = p.communicate()[0]
  155. # uscan exits with 1 in case of uptodate and when an error occured.
  156. # Don't fail in the uptodate case:
  157. self._parse_uptodate(out)
  158. if not self.uptodate:
  159. if p.returncode:
  160. self._raise_error(out)
  161. else:
  162. self._parse(out)
  163. # vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·: