| 1 | #!/usr/bin/python -O |
1 | #!/usr/bin/python -O |
| 2 | """These functions mainly take ebuild info (grabbed from the database and |
2 | """These functions mainly take ebuild info (grabbed from the database and |
| 3 | convert it to HTML. See the "main" function at the bottom.""" |
3 | convert it to HTML. See the "main" function at the bottom.""" |
| 4 | |
4 | |
| 5 | __revision__ = "$Revision: 1.16 $" |
5 | __revision__ = "$Revision: 1.16.2.3 $" |
| 6 | # $Source: /var/cvsroot/gentoo/src/packages/gentoo.py,v $ |
6 | # $Source: /var/cvsroot/gentoo/src/packages/gentoo.py,v $ |
| 7 | |
7 | |
| 8 | import config |
8 | import config |
| 9 | import os |
9 | import os |
| 10 | import time |
10 | import time |
| … | |
… | |
| 13 | import ebuilddb |
13 | import ebuilddb |
| 14 | import bugs |
14 | import bugs |
| 15 | import changelogs |
15 | import changelogs |
| 16 | from cgi import escape |
16 | from cgi import escape |
| 17 | from urllib import quote |
17 | from urllib import quote |
| 18 | |
18 | from portage_versions import pkgcmp, pkgsplit |
| 19 | endversion={"pre":-2,"p":0,"alpha":-4,"beta":-3,"rc":-1} |
|
|
| 20 | # as there's no reliable way to set {}.keys() order |
|
|
| 21 | # netversion_keys will be used instead of endversion.keys |
|
|
| 22 | # to have fixed search order, so that "pre" is checked |
|
|
| 23 | # before "p" |
|
|
| 24 | endversion_keys = ["pre", "p", "alpha", "beta", "rc"] |
|
|
| 25 | |
19 | |
| 26 | def is_new(db, ebuild): |
20 | def is_new(db, ebuild): |
| 27 | """Check for newness.""" |
21 | """Check for newness.""" |
| 28 | |
22 | |
| 29 | c = db.cursor() |
23 | c = db.cursor() |
| … | |
… | |
| 58 | if not license.strip(): return "?" |
52 | if not license.strip(): return "?" |
| 59 | license = license.replace('|',' ') |
53 | license = license.replace('|',' ') |
| 60 | license = license.replace('(', '') |
54 | license = license.replace('(', '') |
| 61 | license = license.replace(')', '') |
55 | license = license.replace(')', '') |
| 62 | pieces = license.split() |
56 | pieces = license.split() |
| 63 | html = ['<a href="http://www.gentoo.org/cgi-bin/viewcvs.cgi/*checkout*/' |
57 | html = ['<a href="http://sources.gentoo.org/viewcvs.py/*checkout*/' |
| 64 | 'licenses/%s">%s</a>' % (piece, piece) for piece in pieces] |
58 | 'gentoo-x86/licenses/%s">%s</a>' % (piece, piece) for piece in pieces] |
| 65 | return '<br>\n'.join(html) |
59 | return '<br>\n'.join(html) |
| 66 | |
60 | |
| 67 | def package_to_html(pkginfo, db, full=False): |
61 | def package_to_html(pkginfo, db, full=False): |
| 68 | """This function needs a database (db) connection because it performs a |
62 | """This function needs a database (db) connection because it performs a |
| 69 | query_to_dict on the package""" |
63 | query_to_dict on the package""" |
| … | |
… | |
| 181 | """This actually will (should) take either a package or ebuild dict |
175 | """This actually will (should) take either a package or ebuild dict |
| 182 | as an argument""" |
176 | as an argument""" |
| 183 | |
177 | |
| 184 | import forums |
178 | import forums |
| 185 | |
179 | |
| 186 | changelogurl = ('http://www.gentoo.org/cgi-bin/viewcvs.cgi/*checkout*/' |
180 | changelogurl = ('http://sources.gentoo.org/viewcvs.py/*checkout*/' |
| 187 | '%s/%s/ChangeLog' % (pkg['category'],pkg['name'])) |
181 | 'gentoo-x86/%s/%s/ChangeLog' % (pkg['category'],pkg['name'])) |
| 188 | cat_header = '<th class="category">Category</th>' |
182 | cat_header = '<th class="category">Category</th>' |
| 189 | license_header = '<th class="license">License</th>' |
183 | license_header = '<th class="license">License</th>' |
| 190 | category = ('<td class="category">' |
184 | category = ('<td class="category">' |
| 191 | '<a href="%spackages/?category=%s">%s</a></td>' % (config.FEHOME, |
185 | '<a href="%spackages/?category=%s">%s</a></td>' % (config.FEHOME, |
| 192 | pkg['category'], pkg['category'])) |
186 | pkg['category'], pkg['category'])) |
| … | |
… | |
| 220 | '</table>']) |
214 | '</table>']) |
| 221 | |
215 | |
| 222 | def create_similar_pkgs_link(pkg): |
216 | def create_similar_pkgs_link(pkg): |
| 223 | """Create a link to similar packages""" |
217 | """Create a link to similar packages""" |
| 224 | |
218 | |
| 225 | def strip_chars(mystring): |
219 | return '<a href="/similar/?package=%(category)s/%(name)s">Similar</a>' % pkg |
| 226 | newstring = '' |
|
|
| 227 | for char in mystring: |
|
|
| 228 | if char not in string.punctuation: |
|
|
| 229 | newstring = newstring + char |
|
|
| 230 | else: |
|
|
| 231 | newstring = newstring + ' ' |
|
|
| 232 | return newstring |
|
|
| 233 | |
|
|
| 234 | description = strip_chars(pkg['description'].lower()) |
|
|
| 235 | |
|
|
| 236 | words = [word for word in description.split() |
|
|
| 237 | if word and len(word)>2 and word not in config.EXCLUDED_FROM_SIMILAR] |
|
|
| 238 | words = words[:config.SIMILAR_MAX_WORDS] + [pkg['name']] |
|
|
| 239 | #query = ['[[:<:]]%s[[:>:]].*' % word for word in words] |
|
|
| 240 | query = ['[[:<:]]%s.*' % word for word in words] |
|
|
| 241 | query = '(%s){%s,}' % ('|'.join(query), config.SIMILAR_MIN_MATCHES) |
|
|
| 242 | url = '%ssearch/?sstring=%s' % (config.FEHOME, escape(query)) |
|
|
| 243 | return '<a href="%s">Similar</a>' % url |
|
|
| 244 | |
220 | |
| 245 | def create_related_bugs_link(pkg): |
221 | def create_related_bugs_link(pkg): |
| 246 | """Create a link to related bugs""" |
222 | """Create a link to related bugs""" |
| 247 | |
223 | |
| 248 | url = ('http://bugs.gentoo.org/buglist.cgi?query_format=' |
224 | url = ('http://bugs.gentoo.org/buglist.cgi?query_format=' |
| … | |
… | |
| 297 | extra = ' AND ((%s) OR (%s)) ' % (stable_extra, testing_extra) |
273 | extra = ' AND ((%s) OR (%s)) ' % (stable_extra, testing_extra) |
| 298 | |
274 | |
| 299 | if new: |
275 | if new: |
| 300 | extra = ('%s AND package.new=1 ' % extra) |
276 | extra = ('%s AND package.new=1 ' % extra) |
| 301 | |
277 | |
| 302 | query = """SELECT ebuild.category,ebuild.name,version,when_found,description, |
278 | query = """SELECT ebuild.category,ebuild.name,version,ebuild.when_found,description, |
| 303 | changelog,arch,homepage,license,is_masked FROM ebuild,package WHERE ebuild.name=\ |
279 | changelog,arch,homepage,license,is_masked FROM ebuild,package WHERE ebuild.name=\ |
| 304 | package.name AND ebuild.category=package.category %s ORDER by when_found DESC \ |
280 | package.name AND ebuild.category=package.category %s ORDER by ebuild.when_found DESC \ |
| 305 | LIMIT %s""" % (extra,max) |
281 | LIMIT %s""" % (extra,max) |
| 306 | c.execute(query) |
282 | c.execute(query) |
| 307 | results = c.fetchall() |
283 | results = c.fetchall() |
| 308 | return results |
284 | return results |
| 309 | |
285 | |
| … | |
… | |
| 348 | def cmp_ebuilds(a, b): |
324 | def cmp_ebuilds(a, b): |
| 349 | """Compare two ebuilds""" |
325 | """Compare two ebuilds""" |
| 350 | fields_a = pkgsplit('%s-%s' % (a['name'], a['version'])) |
326 | fields_a = pkgsplit('%s-%s' % (a['name'], a['version'])) |
| 351 | fields_b = pkgsplit('%s-%s' % (b['name'], b['version'])) |
327 | fields_b = pkgsplit('%s-%s' % (b['name'], b['version'])) |
| 352 | return pkgcmp(fields_a, fields_b) |
328 | return pkgcmp(fields_a, fields_b) |
| 353 | |
|
|
| 354 | pkgcache={} |
|
|
| 355 | |
|
|
| 356 | def pkgsplit(mypkg,silent=1): |
|
|
| 357 | try: |
|
|
| 358 | if not pkgcache[mypkg]: |
|
|
| 359 | return None |
|
|
| 360 | return pkgcache[mypkg][:] |
|
|
| 361 | except KeyError: |
|
|
| 362 | pass |
|
|
| 363 | myparts=string.split(mypkg,'-') |
|
|
| 364 | if len(myparts)<2: |
|
|
| 365 | if not silent: |
|
|
| 366 | print "!!! Name error in",mypkg+": missing a version or name part." |
|
|
| 367 | pkgcache[mypkg]=None |
|
|
| 368 | return None |
|
|
| 369 | for x in myparts: |
|
|
| 370 | if len(x)==0: |
|
|
| 371 | if not silent: |
|
|
| 372 | print "!!! Name error in",mypkg+": empty \"-\" part." |
|
|
| 373 | pkgcache[mypkg]=None |
|
|
| 374 | return None |
|
|
| 375 | #verify rev |
|
|
| 376 | revok=0 |
|
|
| 377 | myrev=myparts[-1] |
|
|
| 378 | if len(myrev) and myrev[0]=="r": |
|
|
| 379 | try: |
|
|
| 380 | int(myrev[1:]) |
|
|
| 381 | revok=1 |
|
|
| 382 | except SystemExit, e: |
|
|
| 383 | raise |
|
|
| 384 | except: |
|
|
| 385 | pass |
|
|
| 386 | if revok: |
|
|
| 387 | if ververify(myparts[-2]): |
|
|
| 388 | if len(myparts)==2: |
|
|
| 389 | pkgcache[mypkg]=None |
|
|
| 390 | return None |
|
|
| 391 | else: |
|
|
| 392 | for x in myparts[:-2]: |
|
|
| 393 | if ververify(x): |
|
|
| 394 | pkgcache[mypkg]=None |
|
|
| 395 | return None |
|
|
| 396 | #names can't have versiony looking parts |
|
|
| 397 | myval=[string.join(myparts[:-2],"-"),myparts[-2],myparts[-1]] |
|
|
| 398 | pkgcache[mypkg]=myval |
|
|
| 399 | return myval |
|
|
| 400 | else: |
|
|
| 401 | pkgcache[mypkg]=None |
|
|
| 402 | return None |
|
|
| 403 | |
|
|
| 404 | elif ververify(myparts[-1],silent=silent): |
|
|
| 405 | if len(myparts)==1: |
|
|
| 406 | if not silent: |
|
|
| 407 | print "!!! Name error in",mypkg+": missing name part." |
|
|
| 408 | pkgcache[mypkg]=None |
|
|
| 409 | return None |
|
|
| 410 | else: |
|
|
| 411 | for x in myparts[:-1]: |
|
|
| 412 | if ververify(x): |
|
|
| 413 | if not silent: |
|
|
| 414 | print "!!! Name error in",mypkg+": multiple version parts." |
|
|
| 415 | pkgcache[mypkg]=None |
|
|
| 416 | return None |
|
|
| 417 | myval=[string.join(myparts[:-1],"-"),myparts[-1],"r0"] |
|
|
| 418 | pkgcache[mypkg]=myval[:] |
|
|
| 419 | return myval |
|
|
| 420 | else: |
|
|
| 421 | pkgcache[mypkg]=None |
|
|
| 422 | return None |
|
|
| 423 | |
|
|
| 424 | vercache={} |
|
|
| 425 | def ververify(myorigval,silent=1): |
|
|
| 426 | try: |
|
|
| 427 | return vercache[myorigval] |
|
|
| 428 | except KeyError: |
|
|
| 429 | pass |
|
|
| 430 | if len(myorigval)==0: |
|
|
| 431 | if not silent: |
|
|
| 432 | print "!!! Name error: package contains empty \"-\" part." |
|
|
| 433 | return 0 |
|
|
| 434 | myval=string.split(myorigval,'.') |
|
|
| 435 | if len(myval)==0: |
|
|
| 436 | if not silent: |
|
|
| 437 | print "!!! Name error: empty version string." |
|
|
| 438 | vercache[myorigval]=0 |
|
|
| 439 | return 0 |
|
|
| 440 | #all but the last version must be a numeric |
|
|
| 441 | for x in myval[:-1]: |
|
|
| 442 | if not len(x): |
|
|
| 443 | if not silent: |
|
|
| 444 | print "!!! Name error in",myorigval+": two decimal points in a row" |
|
|
| 445 | vercache[myorigval]=0 |
|
|
| 446 | return 0 |
|
|
| 447 | try: |
|
|
| 448 | foo=int(x) |
|
|
| 449 | except SystemExit, e: |
|
|
| 450 | raise |
|
|
| 451 | except: |
|
|
| 452 | if not silent: |
|
|
| 453 | print "!!! Name error in",myorigval+": \""+x+"\" is not a valid version component." |
|
|
| 454 | vercache[myorigval]=0 |
|
|
| 455 | return 0 |
|
|
| 456 | if not len(myval[-1]): |
|
|
| 457 | if not silent: |
|
|
| 458 | print "!!! Name error in",myorigval+": two decimal points in a row" |
|
|
| 459 | vercache[myorigval]=0 |
|
|
| 460 | return 0 |
|
|
| 461 | try: |
|
|
| 462 | foo=int(myval[-1]) |
|
|
| 463 | vercache[myorigval]=1 |
|
|
| 464 | return 1 |
|
|
| 465 | except SystemExit, e: |
|
|
| 466 | raise |
|
|
| 467 | except: |
|
|
| 468 | pass |
|
|
| 469 | #ok, our last component is not a plain number or blank, let's continue |
|
|
| 470 | if myval[-1][-1] in string.lowercase: |
|
|
| 471 | try: |
|
|
| 472 | foo=int(myval[-1][:-1]) |
|
|
| 473 | vercache[myorigval]=1 |
|
|
| 474 | return 1 |
|
|
| 475 | # 1a, 2.0b, etc. |
|
|
| 476 | except SystemExit, e: |
|
|
| 477 | raise |
|
|
| 478 | except: |
|
|
| 479 | pass |
|
|
| 480 | #ok, maybe we have a 1_alpha or 1_beta2; let's see |
|
|
| 481 | #ep="endpart" |
|
|
| 482 | ep=string.split(myval[-1],"_") |
|
|
| 483 | if len(ep)!=2: |
|
|
| 484 | if not silent: |
|
|
| 485 | print "!!! Name error in",myorigval |
|
|
| 486 | vercache[myorigval]=0 |
|
|
| 487 | return 0 |
|
|
| 488 | try: |
|
|
| 489 | foo=int(ep[0][-1]) |
|
|
| 490 | chk=ep[0] |
|
|
| 491 | except SystemExit, e: |
|
|
| 492 | raise |
|
|
| 493 | except: |
|
|
| 494 | # because it's ok last char is not numeric. example: foo-1.0.0a_pre1 |
|
|
| 495 | chk=ep[0][:-1] |
|
|
| 496 | |
|
|
| 497 | try: |
|
|
| 498 | foo=int(chk) |
|
|
| 499 | except SystemExit, e: |
|
|
| 500 | raise |
|
|
| 501 | except: |
|
|
| 502 | #this needs to be numeric or numeric+single letter, |
|
|
| 503 | #i.e. the "1" in "1_alpha" or "1a_alpha" |
|
|
| 504 | if not silent: |
|
|
| 505 | print "!!! Name error in",myorigval+": characters before _ must be numeric or numeric+single letter" |
|
|
| 506 | vercache[myorigval]=0 |
|
|
| 507 | return 0 |
|
|
| 508 | for mye in endversion_keys: |
|
|
| 509 | if ep[1][0:len(mye)]==mye: |
|
|
| 510 | if len(mye)==len(ep[1]): |
|
|
| 511 | #no trailing numeric; ok |
|
|
| 512 | vercache[myorigval]=1 |
|
|
| 513 | return 1 |
|
|
| 514 | else: |
|
|
| 515 | try: |
|
|
| 516 | foo=int(ep[1][len(mye):]) |
|
|
| 517 | vercache[myorigval]=1 |
|
|
| 518 | return 1 |
|
|
| 519 | except SystemExit, e: |
|
|
| 520 | raise |
|
|
| 521 | except: |
|
|
| 522 | #if no endversions work, *then* we return 0 |
|
|
| 523 | pass |
|
|
| 524 | if not silent: |
|
|
| 525 | print "!!! Name error in",myorigval |
|
|
| 526 | vercache[myorigval]=0 |
|
|
| 527 | return 0 |
|
|
| 528 | |
|
|
| 529 | def relparse(myver): |
|
|
| 530 | "converts last version part into three components" |
|
|
| 531 | number=0 |
|
|
| 532 | suffix=0 |
|
|
| 533 | endtype=0 |
|
|
| 534 | endnumber=0 |
|
|
| 535 | |
|
|
| 536 | mynewver=string.split(myver,"_") |
|
|
| 537 | myver=mynewver[0] |
|
|
| 538 | |
|
|
| 539 | #normal number or number with letter at end |
|
|
| 540 | divider=len(myver)-1 |
|
|
| 541 | if myver[divider:] not in "1234567890": |
|
|
| 542 | #letter at end |
|
|
| 543 | suffix=ord(myver[divider:]) |
|
|
| 544 | number=string.atof(myver[0:divider]) |
|
|
| 545 | else: |
|
|
| 546 | number=string.atof(myver) |
|
|
| 547 | |
|
|
| 548 | if len(mynewver)==2: |
|
|
| 549 | #an endversion |
|
|
| 550 | for x in endversion_keys: |
|
|
| 551 | elen=len(x) |
|
|
| 552 | if mynewver[1][:elen] == x: |
|
|
| 553 | endtype=endversion[x] |
|
|
| 554 | try: |
|
|
| 555 | endnumber=string.atof(mynewver[1][elen:]) |
|
|
| 556 | except: |
|
|
| 557 | endnumber=0 |
|
|
| 558 | break |
|
|
| 559 | return [number,suffix,endtype,endnumber] |
|
|
| 560 | |
|
|
| 561 | # vercmp: |
|
|
| 562 | # ripped from portage.py to prevent having to import |
|
|
| 563 | vcmpcache={} |
|
|
| 564 | def vercmp(val1,val2): |
|
|
| 565 | if val1==val2: |
|
|
| 566 | #quick short-circuit |
|
|
| 567 | return 0 |
|
|
| 568 | valkey=val1+" "+val2 |
|
|
| 569 | try: |
|
|
| 570 | return vcmpcache[valkey] |
|
|
| 571 | try: |
|
|
| 572 | return -vcmpcache[val2+" "+val1] |
|
|
| 573 | except KeyError: |
|
|
| 574 | pass |
|
|
| 575 | except KeyError: |
|
|
| 576 | pass |
|
|
| 577 | |
|
|
| 578 | # consider 1_p2 vc 1.1 |
|
|
| 579 | # after expansion will become (1_p2,0) vc (1,1) |
|
|
| 580 | # then 1_p2 is compared with 1 before 0 is compared with 1 |
|
|
| 581 | # to solve the bug we need to convert it to (1,0_p2) |
|
|
| 582 | # by splitting _prepart part and adding it back _after_expansion |
|
|
| 583 | val1_prepart = val2_prepart = '' |
|
|
| 584 | if val1.count('_'): |
|
|
| 585 | val1, val1_prepart = val1.split('_', 1) |
|
|
| 586 | if val2.count('_'): |
|
|
| 587 | val2, val2_prepart = val2.split('_', 1) |
|
|
| 588 | |
|
|
| 589 | # replace '-' by '.' |
|
|
| 590 | # FIXME: Is it needed? can val1/2 contain '-'? |
|
|
| 591 | val1=string.split(val1,'-') |
|
|
| 592 | if len(val1)==2: |
|
|
| 593 | val1[0]=val1[0]+"."+val1[1] |
|
|
| 594 | val2=string.split(val2,'-') |
|
|
| 595 | if len(val2)==2: |
|
|
| 596 | val2[0]=val2[0]+"."+val2[1] |
|
|
| 597 | |
|
|
| 598 | val1=string.split(val1[0],'.') |
|
|
| 599 | val2=string.split(val2[0],'.') |
|
|
| 600 | |
|
|
| 601 | #add back decimal point so that .03 does not become "3" ! |
|
|
| 602 | for x in range(1,len(val1)): |
|
|
| 603 | if val1[x][0] == '0' : |
|
|
| 604 | val1[x]='.' + val1[x] |
|
|
| 605 | for x in range(1,len(val2)): |
|
|
| 606 | if val2[x][0] == '0' : |
|
|
| 607 | val2[x]='.' + val2[x] |
|
|
| 608 | |
|
|
| 609 | # extend version numbers |
|
|
| 610 | if len(val2)<len(val1): |
|
|
| 611 | val2.extend(["0"]*(len(val1)-len(val2))) |
|
|
| 612 | elif len(val1)<len(val2): |
|
|
| 613 | val1.extend(["0"]*(len(val2)-len(val1))) |
|
|
| 614 | |
|
|
| 615 | # add back _prepart tails |
|
|
| 616 | if val1_prepart: |
|
|
| 617 | val1[-1] += '_' + val1_prepart |
|
|
| 618 | if val2_prepart: |
|
|
| 619 | val2[-1] += '_' + val2_prepart |
|
|
| 620 | #The above code will extend version numbers out so they |
|
|
| 621 | #have the same number of digits. |
|
|
| 622 | for x in range(0,len(val1)): |
|
|
| 623 | cmp1=relparse(val1[x]) |
|
|
| 624 | cmp2=relparse(val2[x]) |
|
|
| 625 | for y in range(0,4): |
|
|
| 626 | myret=cmp1[y]-cmp2[y] |
|
|
| 627 | if myret != 0: |
|
|
| 628 | vcmpcache[valkey]=myret |
|
|
| 629 | return myret |
|
|
| 630 | vcmpcache[valkey]=0 |
|
|
| 631 | return 0 |
|
|
| 632 | |
|
|
| 633 | # pkgcmp: |
|
|
| 634 | # ripped from portage.py to prevent having to import |
|
|
| 635 | def pkgcmp(pkg1,pkg2): |
|
|
| 636 | """if returnval is less than zero, then pkg2 is newer than pkg1, zero if equal and positive if older.""" |
|
|
| 637 | if pkg1[0] != pkg2[0]: |
|
|
| 638 | return None |
|
|
| 639 | mycmp=vercmp(pkg1[1],pkg2[1]) |
|
|
| 640 | if mycmp>0: |
|
|
| 641 | return 1 |
|
|
| 642 | if mycmp<0: |
|
|
| 643 | return -1 |
|
|
| 644 | r1=int(pkg1[2][1:]) |
|
|
| 645 | r2=int(pkg2[2][1:]) |
|
|
| 646 | if r1>r2: |
|
|
| 647 | return 1 |
|
|
| 648 | if r2>r1: |
|
|
| 649 | return -1 |
|
|
| 650 | return 0 |
|
|
| 651 | |
329 | |
| 652 | def ebuilds_to_rss(fp, ebuilds, simple=False, subtitle=""): |
330 | def ebuilds_to_rss(fp, ebuilds, simple=False, subtitle=""): |
| 653 | """write out ebuild info to RSS file (fp)""" |
331 | """write out ebuild info to RSS file (fp)""" |
| 654 | |
332 | |
| 655 | # web link for RSS feed |
333 | # web link for RSS feed |