Revert to r15
[g-pypi.git] / g_pypi / enamer.py
1 #!/usr/bin/env python
2 # pylint: disable-msg=C0103,E0611,W0511
3
4 """
5 enamer.py
6 =========
7
8 Functions for extracting useful info from a pkg URI
9 such as PN, PV, MY_P, SRC_URI
10
11 * Examples of what it can detect/convert:
12     (See test_enamer.py for full capabilities)
13
14     http://www.foo.com/pkgfoo-1.0.tbz2
15     PN="pkgfoo"
16     PV="1.0"
17     Ebuild name: pkgfoo-1.0.ebuild
18     SRC_URI="http://www.foo.com/${P}.tbz2"
19
20     http://www.foo.com/PkgFoo-1.0.tbz2
21     PN="pkgfoo"
22     PV="1.0"
23     Ebuild name: pkgfoo-1.0.ebuild
24     MY_P="PkgFoo-${PV}"
25     SRC_URI="http://www.foo.com/${MY_P}.tbz2"
26
27     http://www.foo.com/pkgfoo_1.0.tbz2
28     PN="pkgfoo"
29     PV="1.0"
30     Ebuild name: pkgfoo-1.0.ebuild
31     MY_P="${PN}_${PV}"
32     SRC_URI="http://www.foo.com/${MY_P}.tbz2"
33
34     http://www.foo.com/PKGFOO_1.0.tbz2
35     PN="pkgfoo"
36     PV="1.0"
37     Ebuild name: pkgfoo-1.0.ebuild
38     MY_P="PKGFOO_${PV}"
39     SRC_URI="http://www.foo.com/${MY_P}.tbz2"
40
41     http://www.foo.com/pkg-foo-1.0_beta1.tbz2
42     PN="pkg-foo"
43     PV="1.0_beta1"
44     Ebuild name: pkg-foo-1.0_beta1.ebuild
45     SRC_URI="http://www.foo.com/${P}.tbz2"
46
47 """
48
49 import urlparse
50 import re
51
52 from portage import pkgsplit
53
54 try:
55     #portage >=2.2
56     from portage import dep as portage_dep
57 except ImportError:
58     #portage <=2.1
59     from portage import portage_dep
60
61 try:
62     #portage >=2.2
63     from portage import exception as portage_exception
64 except ImportError:
65     #portage <=2.1
66     from portage import portage_exception
67
68 __docformat__ = 'restructuredtext'
69
70
71 def get_filename(uri):
72     """
73     Return file name minus extension from src_uri
74     e.g. http://somesite.com/foobar-1.0.tar.gz will yield foobar-1.0
75     
76     @param uri: URI to package with no variables substitution
77     @type uri: string
78
79     @returns: string
80
81     """
82     path = urlparse.urlparse(uri)[2]
83     path = path.split('/')
84     return strip_ext(path[len(path)-1])
85
86 def strip_ext(fname):
87     """Strip possible extensions from filename."""
88     valid_extensions = [".zip", ".tgz", ".tar.gz", ".tar.bz2", ".tbz2"]
89     for ext in valid_extensions:
90         if fname.endswith(ext):
91             fname = fname.replace(ext, "")
92             break
93     return fname
94
95 def is_valid_uri(uri):
96     """
97     Check if URI's addressing scheme is valid
98
99     @param uri: URI to pacakge with no variable substitution
100     @type uri: string
101     
102     @returns: True or False
103     """
104     if uri.startswith("http:") or uri.startswith("ftp:") or \
105             uri.startswith("mirror:") or uri.startswith("svn:"):
106         return True
107     else:
108         return False
109
110 def parse_sourceforge_uri(uri):
111     """
112     Change URI to mirror://sourceforge format
113     Also determines a homepage string which can be used if the metadata 
114     doesn't have Home_page
115
116     @param uri: URI to pacakge with no variable substitution
117     @type uri: string
118     
119     @returns: tuple (uri string, homepage string)
120     """
121     uri_out = homepage = ""
122     tst_uri = urlparse.urlparse(uri)
123     
124     host = tst_uri[1]
125     upath = tst_uri[2]
126     if upath.startswith("/sourceforge"):
127         upath = upath[12:]
128     if ("sourceforge" in host) or (host.endswith("sf.net")):
129         uri_out = 'mirror://sourceforge%s' % upath
130         homepage = "http://sourceforge.net/projects/%s/" % \
131                    upath.split("/")[1]
132     return uri_out, homepage
133
134 def is_good_filename(uri):
135     """If filename is sane enough to deduce PN & PV, return pkgsplit results"""
136     if is_valid_uri(uri):
137         psplit = split_p(uri)
138         if psplit and psplit[0].islower():
139             return psplit
140
141 def split_p(uri):
142     """Try to split a URI into PN, PV"""
143     p = get_filename(uri)
144     psplit = pkgsplit(p)
145     return psplit
146
147 def get_components(uri):
148     """Split uri into pn and pv and new uri"""
149     p = get_filename(uri)
150     psplit = split_p(uri)
151     uri_out = uri.replace(p, "${P}") 
152     pn = psplit[0].lower()
153     pv = psplit[1]
154     return uri_out, pn, pv
155
156 def get_myp(uri):
157     """Return MY_P and new uri with MY_P in it"""
158     my_p = get_filename(uri)
159     uri_out = uri.replace(my_p, "${MY_P}") 
160     return uri_out, my_p
161
162 def guess_components(my_p):
163     """Try to break up raw MY_P into PN and PV"""
164     pn, pv = "", ""
165
166     # Ok, we just have one automagical test here.
167     # We should look at versionator.eclass for inspiration
168     # and then come up with several functions.
169     my_p = my_p.replace("_", "-")
170
171     psplit = pkgsplit(my_p)
172     if psplit:
173         pn = psplit[0].lower()
174         pv = psplit[1]
175     return pn, pv
176
177
178 def bad_pv(up_pn, up_pv, pn="", pv="", my_pn="", my_pv=""):
179     """
180     Can't determine PV from upstream's version.
181     Do our best with some well-known versioning schemes:
182
183     1.0a1 (1.0_alpha1)
184     1.0-a1 (1.0_alpha1)
185     1.0b1 (1.0_beta1)
186     1.0-b1 (1.0_beta1)
187     1.0-r1234 (1.0_pre1234)
188     1.0dev-r1234 (1.0_pre1234)
189     1.0.dev-r1234 (1.0_pre1234)
190
191     regex match.groups:
192     pkgfoo-1.0.dev-r1234
193     group 1 pv major (1.0)
194     group 2 entire suffix (.dev-r1234)
195     group 3 replace this with portage suffix (.dev-r)
196     group 4 suffix version (1234)
197
198     The order of the regex's is significant. For instance if you have
199     .dev-r123, dev-r123 and -r123 you should order your regex's in
200     that order.
201     
202     The number of regex's could have been reduced, but we use four
203     number of match.groups every time to simplify the code
204
205     The _pre suffix is most-likely incorrect. There is no 'dev'
206     prefix used by portage, the 'earliest' there is is '_alpha'.
207     The chronological portage release versions are:
208     _alpha
209     _beta
210     _pre
211     _rc
212     release
213     _p 
214     """
215     my_p = ""
216     suf_matches = {
217             '_pre': ['(.*)((\.dev-r)([0-9]+))$', 
218                      '(.*)((dev-r)([0-9]+))$',
219                      '(.*)((-r)([0-9]+))$'],
220             '_alpha': ['(.*)((-a)([0-9]+))$', '(.*)((a)([0-9]+))$'],
221             '_beta': ['(.*)((-b)([0-9]+))$', '(.*)((b)([0-9]+))$'],
222             '_rc': ['(.*)((\.rc)([0-9]+))$', '(.*)((-rc)([0-9]+))$',
223                     '(.*)((rc)([0-9]+))$', '(.*)((-c)([0-9]+))$',
224                     '(.*)((\.c)([0-9]+))$', '(.*)((c)([0-9]+))$'],
225             }
226     sufs = suf_matches.keys()
227     rs_match = None
228     for this_suf in sufs:
229         if rs_match:
230             break
231         for regex in suf_matches[this_suf]:
232             rsuffix_regex = re.compile(regex)
233             rs_match = rsuffix_regex.match(up_pv)
234             if rs_match:
235                 portage_suffix = this_suf
236                 break
237     if rs_match:
238         #e.g. 1.0.dev-r1234
239         major_ver = rs_match.group(1) # 1.0
240         #whole_suffix = rs_match.group(2) #.dev-r1234
241         replace_me = rs_match.group(3) #.dev-r
242         rev = rs_match.group(4) #1234
243         if not up_pn.islower():
244             my_pn = up_pn
245             pn = up_pn.lower()
246         pv = major_ver + portage_suffix + rev
247         if my_pn:
248             my_p = "${MY_PN}-${MY_PV}"
249         else:
250             my_p = "${PN}-${MY_PV}"
251         my_pv = "${PV/%s/%s}" % (portage_suffix, replace_me)
252
253     #Single suffixes with no numeric component are simply removed.
254     else:
255         bad_suffixes = [".dev", "-dev", "dev", ".final", "-final", "final"]
256         for suffix in bad_suffixes:
257             if up_pv.endswith(suffix):
258                 my_pv = "${PV}%s" % suffix
259                 my_p = "${PN}-${MY_PV}"
260                 pn = up_pn
261                 pv = up_pv[:-(len(suffix))]
262                 if not pn.islower():
263                     if not my_pn:
264                         my_pn = pn
265                     pn = pn.lower()
266                 break
267     return pn, pv, my_p, my_pn, my_pv
268
269 def sanitize_uri(uri):
270     """
271     Return URI without any un-needed extension.
272     e.g. http://downloads.sourceforge.net/pythonreports/PythonReports-0.3.1.tar.gz?modtime=1182702645&big_mirror=0
273     would have everything after '?' stripped
274
275     @param uri: URI to pacakge with no variable substitution
276     @type uri: string
277     
278     @returns: string
279
280
281     """
282     return uri
283
284 def get_vars(uri, up_pn, up_pv, pn="", pv="", my_pn="", my_pv="", my_p=""):
285     """
286     Determine P* and MY_* variables
287
288     Don't modify this to accept new URI schemes without writing new 
289     test_enamer unit tests
290
291     This function makes me weep and gives me nightmares.
292     
293     """
294     my_p = my_p_raw = ""
295     uri = sanitize_uri(uri)
296     sf_uri, _sf_homepage = parse_sourceforge_uri(uri)
297     if sf_uri:
298         uri = sf_uri
299         #XXX _sf_homepage can be used if package metadata doesn't have one
300
301
302     #Make sure we have a valid PV
303
304     #Test for PV with -r1234 suffix
305     #Portage uses -r suffixes for it's own ebuild revisions so
306     #we have to convert it to _pre or _alpha etc.
307     try:
308         tail = up_pv.split("-")[-1:][0][0]
309         #we have a version with a -r[nnn] suffix
310         if tail == "r":
311             pn, pv, my_p, my_pn, my_pv = \
312                 bad_pv(up_pn, up_pv, pn, pv, my_pn, my_pv)
313     except:
314         pass
315
316     if not portage_dep.isvalidatom("=dev-python/%s-%s" % (up_pn, up_pv)):
317         pn, pv, my_p, my_pn, my_pv = \
318             bad_pv(up_pn, up_pv, pn, pv, my_pn, my_pv)
319
320     #No PN or PV given on command-line, try upstream's name/version
321     if not pn and not pv:
322         #Try to determine pn and pv from uri
323         parts = split_p(uri)
324         if parts:
325             # pylint: disable-msg=W0612
326             # unused variable 'rev'
327             # The 'rev' is never used because these are
328             # new ebuilds being created.
329             pn, pv, rev = parts
330         else:
331             pn = up_pn
332             pv = up_pv
333     #Try upstream's version if it could't be determined from uri or cli option
334     elif pn and not pv:
335         pv = up_pv
336     elif not pn and pv:
337         pn = up_pn.lower()
338
339     if not pn.islower():
340         #up_pn is lower but uri has upper-case
341         if not my_pn:
342             my_pn = pn
343         pn = pn.lower()
344     p = "%s-%s" % (pn, pv)
345
346
347     #Make sure we have a valid P
348     if not portage_dep.isvalidatom("=dev-python/%s-%s" % (pn, pv)):
349         if not portage_dep.isjustname("dev-python/%s-%s" % (pn, pv)):
350             raise portage_exception.InvalidPackageName(pn)
351         else:
352             raise portage_exception.InvalidVersionString(pv)
353
354     if not my_pn:
355         my_pn = "-".join(my_p.split("-")[:-1])
356         if (my_pn == pn) or (my_pn == "${PN}"):
357             my_pn = ""
358
359     if my_p:
360         if my_p == "%s-%s" % (my_pn, "${PV}"):
361             my_p = "${MY_PN}-${PV}"
362         elif my_p == "%s-%s" % (my_pn, my_pv):
363             my_p = "${MY_PN}-${MY_PV}"
364         elif my_p == "%s-%s" % ("${PN}", my_pv):
365             my_p = "${PN}-${MY_PV}"
366         else:
367             my_p = my_p.replace(pn, "${PN}")
368             my_p = my_p.replace(pv, "${PV}")
369
370     if my_pn and not my_p:
371         if my_pv:
372                 my_p = '%s-%s' % (my_pn, my_pv)
373         elif not my_pv:
374                 my_p = '%s-%s' % (my_pn, '${PV}')
375         my_pn = ''
376
377     #Check if we need to use MY_P based on src's uri
378     if my_p:
379         src_uri, my_p_raw = get_myp(uri)
380     else:
381         src_uri, my_p, my_p_raw = get_src_uri(uri)
382
383     return {'pn': pn,
384             'pv': pv,
385             'p': p,
386             'my_p': my_p,
387             'my_pn': my_pn,
388             'my_pv': my_pv,
389             'my_p_raw': my_p_raw,
390             'src_uri': src_uri,
391             }
392
393 def get_src_uri(uri):
394     """Return src_uri"""
395     my_p = my_p_raw = ''
396     if is_good_filename(uri):
397         src_uri, pn, pv = get_components(uri)
398     else:
399         src_uri, my_p = get_myp(uri)
400         pn, pv = guess_components(my_p)
401         if pn and pv:
402             my_p_raw = my_p
403             my_p = my_p.replace(pn, "${PN}")
404             my_p = my_p.replace(pv, "${PV}")
405
406     return src_uri, my_p, my_p_raw
407