Fixed indentation.
[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     return pkgsplit(p)
145
146 def get_components(uri):
147     """Split uri into pn and pv and new uri"""
148     p = get_filename(uri)
149     psplit = split_p(uri)
150     uri_out = uri.replace(p, "${P}") 
151     pn = psplit[0].lower()
152     pv = psplit[1]
153     return uri_out, pn, pv
154
155 def get_myp(uri):
156     """Return MY_P and new uri with MY_P in it"""
157     my_p = get_filename(uri)
158     uri_out = uri.replace(my_p, "${MY_P}") 
159     return uri_out, my_p
160
161 def guess_components(my_p):
162     """Try to break up raw MY_P into PN and PV"""
163     pn, pv = "", ""
164
165     # Ok, we just have one automagical test here.
166     # We should look at versionator.eclass for inspiration
167     # and then come up with several functions.
168     my_p = my_p.replace("_", "-")
169
170     psplit = pkgsplit(my_p)
171     if psplit:
172         pn = psplit[0].lower()
173         pv = psplit[1]
174     return pn, pv
175
176
177 def bad_pv(up_pn, up_pv, pn="", pv="", my_pn="", my_pv=""):
178     """
179     Can't determine PV from upstream's version.
180     Do our best with some well-known versioning schemes:
181
182     1.0a1 (1.0_alpha1)
183     1.0-a1 (1.0_alpha1)
184     1.0b1 (1.0_beta1)
185     1.0-b1 (1.0_beta1)
186     1.0-r1234 (1.0_pre1234)
187     1.0dev-r1234 (1.0_pre1234)
188     1.0.dev-r1234 (1.0_pre1234)
189
190     regex match.groups:
191     pkgfoo-1.0.dev-r1234
192     group 1 pv major (1.0)
193     group 2 entire suffix (.dev-r1234)
194     group 3 replace this with portage suffix (.dev-r)
195     group 4 suffix version (1234)
196
197     The order of the regex's is significant. For instance if you have
198     .dev-r123, dev-r123 and -r123 you should order your regex's in
199     that order.
200     
201     The number of regex's could have been reduced, but we use four
202     number of match.groups every time to simplify the code
203
204     The _pre suffix is most-likely incorrect. There is no 'dev'
205     prefix used by portage, the 'earliest' there is is '_alpha'.
206     The chronological portage release versions are:
207     _alpha
208     _beta
209     _pre
210     _rc
211     release
212     _p 
213     """
214     my_p = ""
215     suf_matches = {
216             '_pre': ['(.*)((\.dev-r)([0-9]+))$', 
217                      '(.*)((dev-r)([0-9]+))$',
218                      '(.*)((-r)([0-9]+))$'],
219             '_alpha': ['(.*)((-a)([0-9]+))$', '(.*)((a)([0-9]+))$'],
220             '_beta': ['(.*)((-b)([0-9]+))$', '(.*)((b)([0-9]+))$'],
221             '_rc': ['(.*)((\.rc)([0-9]+))$', '(.*)((-rc)([0-9]+))$',
222                     '(.*)((rc)([0-9]+))$', '(.*)((-c)([0-9]+))$',
223                     '(.*)((\.c)([0-9]+))$', '(.*)((c)([0-9]+))$'],
224             }
225     sufs = suf_matches.keys()
226     rs_match = None
227     for this_suf in sufs:
228         if rs_match:
229             break
230         for regex in suf_matches[this_suf]:
231             rsuffix_regex = re.compile(regex)
232             rs_match = rsuffix_regex.match(up_pv)
233             if rs_match:
234                 portage_suffix = this_suf
235                 break
236     if rs_match:
237         #e.g. 1.0.dev-r1234
238         major_ver = rs_match.group(1) # 1.0
239         #whole_suffix = rs_match.group(2) #.dev-r1234
240         replace_me = rs_match.group(3) #.dev-r
241         rev = rs_match.group(4) #1234
242         if not up_pn.islower():
243             my_pn = up_pn
244             pn = up_pn.lower()
245         pv = major_ver + portage_suffix + rev
246         if my_pn:
247             my_p = "${MY_PN}-${MY_PV}"
248         else:
249             my_p = "${PN}-${MY_PV}"
250         my_pv = "${PV/%s/%s}" % (portage_suffix, replace_me)
251
252     #Single suffixes with no numeric component are simply removed.
253     else:
254         bad_suffixes = [".dev", "-dev", "dev", ".final", "-final", "final"]
255         for suffix in bad_suffixes:
256             if up_pv.endswith(suffix):
257                 my_pv = "${PV}%s" % suffix
258                 my_p = "${PN}-${MY_PV}"
259                 pn = up_pn
260                 pv = up_pv[:-(len(suffix))]
261                 if not pn.islower():
262                     if not my_pn:
263                         my_pn = pn
264                     pn = pn.lower()
265                 break
266     return pn, pv, my_p, my_pn, my_pv
267
268 def sanitize_uri(uri):
269     """
270     Return URI without any un-needed extension.
271     e.g. http://downloads.sourceforge.net/pythonreports/PythonReports-0.3.1.tar.gz?modtime=1182702645&big_mirror=0
272     would have everything after '?' stripped
273
274     @param uri: URI to pacakge with no variable substitution
275     @type uri: string
276     
277     @returns: string
278
279
280     """
281     return uri
282
283 def get_vars(uri, up_pn, up_pv, pn="", pv="", my_pn="", my_pv=""):
284     """
285     Determine P* and MY_* variables
286
287     Don't modify this to accept new URI schemes without writing new 
288     test_enamer unit tests
289
290     This function makes me weep and gives me nightmares.
291     
292     """
293     my_p = my_p_raw = ""
294     uri = sanitize_uri(uri)
295     sf_uri, _sf_homepage = parse_sourceforge_uri(uri)
296     if sf_uri:
297         uri = sf_uri
298         #XXX _sf_homepage can be used if package metadata doesn't have one
299
300
301     #Make sure we have a valid PV
302
303     #Test for PV with -r1234 suffix
304     #Portage uses -r suffixes for it's own ebuild revisions so
305     #we have to convert it to _pre or _alpha etc.
306     try:
307         tail = up_pv.split("-")[-1:][0][0]
308         #we have a version with a -r[nnn] suffix
309         if tail == "r":
310             pn, pv, my_p, my_pn, my_pv = \
311                 bad_pv(up_pn, up_pv, pn, pv, my_pn, my_pv)
312     except:
313         pass
314
315     if not portage_dep.isvalidatom("=dev-python/%s-%s" % (up_pn, up_pv)):
316         pn, pv, my_p, my_pn, my_pv = \
317             bad_pv(up_pn, up_pv, pn, pv, my_pn, my_pv)
318
319     #No PN or PV given on command-line, try upstream's name/version
320     if not pn and not pv:
321         #Try to determine pn and pv from uri
322         parts = split_p(uri)
323         if parts:
324             # pylint: disable-msg=W0612
325             # unused variable 'rev'
326             # The 'rev' is never used because these are
327             # new ebuilds being created.
328             pn, pv, rev = parts
329         else:
330             pn = up_pn
331             pv = up_pv
332     #Try upstream's version if it could't be determined from uri or cli option
333     elif pn and not pv:
334         pv = up_pv
335     elif not pn and pv:
336         pn = up_pn.lower()
337
338     if not pn.islower():
339         #up_pn is lower but uri has upper-case
340         if not my_pn:
341             my_pn = pn
342         pn = pn.lower()
343
344     if "." in pn:
345         my_pn = '${PN/./-}'
346         pn = pn.replace('.', '-')
347         my_p = "${MY_PN}-${PV}"
348
349     p = "%s-%s" % (pn, pv)
350
351     #Check if we need to use MY_P based on src's uri
352     if my_p:
353         src_uri, my_p_raw = get_myp(uri)
354     else:
355         src_uri, my_p, my_p_raw = get_src_uri(uri)
356
357     #Make sure we have a valid P
358     if not portage_dep.isvalidatom("=dev-python/%s-%s" % (pn, pv)):
359         if not portage_dep.isjustname("dev-python/%s-%s" % (pn, pv)):
360             raise portage_exception.InvalidPackageName(pn)
361         else:
362             raise portage_exception.InvalidVersionString(pv)
363
364     if not my_pn:
365         my_pn = "-".join(my_p.split("-")[:-1])
366         if (my_pn == pn) or (my_pn == "${PN}"):
367             my_pn = ""
368
369     if my_p:
370         if my_p == "%s-%s" % (my_pn, "${PV}"):
371             my_p = "${MY_PN}-${PV}"
372         elif my_p == "%s-%s" % (my_pn, my_pv):
373             my_p = "${MY_PN}-${MY_PV}"
374         elif my_p == "%s-%s" % ("${PN}", my_pv):
375             my_p = "${PN}-${MY_PV}"
376         else:
377             my_p = my_p.replace(pn, "${PN}")
378             my_p = my_p.replace(pv, "${PV}")
379
380     return {'pn': pn,
381             'pv': pv,
382             'p': p,
383             'my_p': my_p,
384             'my_pn': my_pn,
385             'my_pv': my_pv,
386             'my_p_raw': my_p_raw,
387             'src_uri': src_uri,
388             }
389
390 def get_src_uri(uri):
391     """Return src_uri"""
392     my_p = my_p_raw = ''
393     if is_good_filename(uri):
394         src_uri, pn, pv = get_components(uri)
395     else:
396         src_uri, my_p = get_myp(uri)
397         pn, pv = guess_components(my_p)
398         if pn and pv:
399             my_p_raw = my_p
400             my_p = my_p.replace(pn, "${PN}")
401             my_p = my_p.replace(pv, "${PV}")
402
403     return src_uri, my_p, my_p_raw
404