#!/usr/bin/env python "Shows the INDEX in the ports tree." # This uses the http server in the standard python library, to avoid relying # on other ports besides python. If performance was required one should use # either a threaded server like in cherrypy or a a server using non blocking # IO like in medusa or karrigell. The program needs that an INDEX file be # present in PORTSDIR to run. It is otherwise impossible to get a correct and # complete list of dependencies for a given package. # Author: Michel Talon # Version 1.0: Release, 14/12/2006 # Version 1.1: Html improvements by Mark Ovens, 18/12/2006 # Licence: BSD (revised) # Date: December 2006 import os, sys, shutil, re import BaseHTTPServer # To fake files using memory. from StringIO import StringIO # Some global variables portdir = "/usr/ports/" # can be replaced at will # Colours used for alternate lines to aid readability colour_1 = '#FFFFFF' colour_2 = '#EEEEEE' if portdir.find('/', -1) == -1 : print "portdir needs a trailing / " sys.exit() __version__ = "1.1" port2pkg = {} # Stores the INDEX in memory categories = {} # Stores categories and their descriptions. collects = [] # Stores sorted list of collections # Regular expression pattern matching URLs. patmat = re.compile('http://[-_.@~/a-zA-Z0-9:\?=&#]*[~/a-zA-Z0-9]') # Some utility functions. def cat(origin) : "Displays the collection to which a port belongs." [coll, softw] = origin.split('/') categ = '/' + coll # / is to please http protocol return categ def desc_tops(): "The directories under portdir." subdirs = os.listdir(portdir) pipe = os.popen("cd " + portdir + " && make -V SUBDIR") subd = pipe.readline().split() pipe.close() for collect in subdirs: if collect in subd: # This is a collection for line in file(portdir + collect + '/Makefile').readlines() : if line.find('COMMENT') >= 0 : categories[collect] = \ line.strip().replace('COMMENT = ','',1) break # Found the comment for collection # Builds the sorted list of collections, stored in collects. desc_tops() collects = categories.keys() collects.sort() def desc_coll(collect) : '''The ports in a collection, lexicographically sorted, as they appear in the dictionnary port2pkg.''' # Should be run only after port2pkg filled. ports = [] for origin in port2pkg.keys() : [col, softw] = origin.split('/') if col == collect : ports.append(softw) ports.sort() return ports # We build the html pages corresponding to title page, category page and port # page, according to the templates found in /usr/ports/Templates. In these # templates dynamic data appear in a form such as %%SUBDIR%%. Here will fill # all these data programmatically. # Builds the title page. title_page = ''' The FreeBSD Ports Collection

The FreeBSD Ports Collection


You are at the top of the ports tree.

For information on how to use the ports tree, please look at "The Ports Collection" section of the FreeBSD handbook on your own FreeBSD machine or at the FreeBSD web site.

Also, if you would like to contribute a new port or fix an existing one, please refer to the Porting Guidelines section of the most current handbook.

Here are the one-line descriptions for each of the directories:


 '''

bg = colour_1

for collec in collects :
    title_page += '''
    
''' if bg == colour_1 : bg = colour_2 else : bg = colour_1 title_page += '''
''' # End title page # Builds collection page for a given collection "collec". def collect_page(collect) : "Fills the template for a collection." coll_page = ''' The FreeBSD Ports Collection (''' + collect + ''')

The FreeBSD Ports Collection ("''' + collect + '''")


You are now in the directory "''' + collect + '''".

This is the one-line description for this category:


''' + categories[collect] + '''


Here are the one-line descriptions for each items in this directory:


Go to parent directory

' + collec \ + ''' ''' + categories[collec] + '''
''' 

    bg = colour_1

    for softw in desc_coll(collect) :
        origin = collect + '/' + softw
        coll_page += "
" + '\n' if bg == colour_1 : bg = colour_2 else : bg = colour_1 # This prints each package name and description in the collection coll_page += '''
" + port2pkg[origin][0]\ + " " + port2pkg[origin][2] + "

Go to parent directory ''' return coll_page # End collection page. # Builds port page for a given port. def port_page(origin) : "Builds the page describing a given port." # Some ports have empty websites, which produces confusing error message. # Better produce a meaningful error message. webSite = port2pkg[origin][8] if webSite == '' : webSite = "/nowebsite" por_page = ''' The FreeBSD Ports Collection (''' + origin + ''')

The FreeBSD Ports Collection (''' + origin + ''')


You are now in the directory for the port "''' + origin + '''" .

The package name of this port is "''' + port2pkg[origin][0] + '''" .

This is the one-line description for this port:


''' + port2pkg[origin][2] + '''


Please read the " description file" for a longer description, and/or visit the "web site" for further informations.

If needed, you may contact the maintainer of this port or the port mailing-list.

This port requires package(s) "''' + port2pkg[origin][6] + '''" to build.

This port requires package(s) "''' + port2pkg[origin][7] + '''" to run.

Go to the top of the ports tree for a summary on how to use the ports collection.


Go up one level | Go to top of ports tree

''' return por_page # End port page. def print_desc(path) : "Fetches and displays the long description file for a port." try : f_des = file(path, 'r') except IOError : return "No detailed description." desc = "" for line in f_des.readlines() : # htmlify to remove <> line = line.replace('&','&') # order important line = line.replace('>','>') line = line.replace('<','<') # make all included WWW references clickable line = patmat.sub('\g<0>', line) desc += line return "
 " + desc + "
" # Now we prepare reading the INDEX file and filling accordingly the dictionary # port2pkg which contains all the required information. def find_index() : "Computes the name of Index file." pipe = os.popen("uname -r") uname = pipe.read() pipe.close() if int(uname[0]) <= 4 : indexx = 'INDEX' else : indexx = 'INDEX-' + uname[0] return indexx # Open the INDEX file. try : index = open(portdir + find_index(),'r') except IOError : print "Cannot run without an INDEX file." sys.exit() # Fill port2pkg with INDEX information for indline in index : port = indline.strip().split('|') orig = port[1].replace(portdir, '', 1) # 1 origin port2pkg[orig] = [port[0], # 0 pkgname -> 0 port[2], # 2 prefix -> 1 port[3], # 3 comment -> 2 port[4], # 4 description -> 3 port[5], # 5 maintainer -> 4 port[6], # 6 categories -> 5 port[7], # 7 build_deps -> 6 port[8], # 8 run_deps -> 7 port[9], # 9 website -> 8 port[10], # 10 extract_deps -> 9 port[11], # 11 patch_deps -> 10 port[12] ] # 12 fetch_deps -> 11 # This is debugging stuff, to show the content of the dictionary without # needing to go through the html engine. def print_port(origin) : "For use in debug_port." entries = ['pkgname', 'origin', 'prefix', 'comment', 'description', 'maintainer', 'categories', 'build_deps', 'run_deps', 'website', 'extract_deps', 'patch_deps', 'fetch_deps'] print entries[0] + " => " + port2pkg[origin][0] print entries[1] + " => " + origin for ind in range(2, 13) : print entries[ind] + " => " + port2pkg[origin][ind - 1] def debug_port() : "For direct examination of a port info." print "Port to describe, e.g. devel/clanlib." while 1 : ans = raw_input('--> ') if ans in ['exit', 'quit', 'q'] : sys.exit() try : print_port(ans) except KeyError : print "Please use name like devel/clanlib, quit to exit." # The main routine which handles http requests, parses them and sends back the # appropriate html pages as computed above. class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler) : '''Handles requests to show infos about ports. It is modelled directly after the one in SimpleHTTPRequestHandler, but adapted to our needs.''' # The principle is to override the request handler in BaseHTTPServer so that # it does what we need. The rest of the connection, including parsing the # requests is done by BaseHTTPServer. According to type of request, GET, # etc. a function do_GET(), etc. is called, we override it. Here HEAD only # sends headers, while GET sends headers and data. server_version = "ShowportsHTTP/" + __version__ def do_GET(self): "Serve a GET request." fil = self.send_head() if fil : self.copyfile(fil, self.wfile) fil.close() def do_HEAD(self): "Serve a HEAD request." fil = self.send_head() if fil : fil.close() def copyfile(self, source, outputfile) : "Like in SimpleHTTPServer." shutil.copyfileobj(source, outputfile) def send_head(self): '''Common code for GET and HEAD commands. This sends the response code and MIME headers.''' # BaseHTTPServer has broken the request into type, path, version. Type is # GET, etc. in our case mostly GET. We need only react to value of path. fil = StringIO() # Buffer, looks like a file ... self.path = self.path.rstrip('/') # Removes some oddities. pth = self.path.split('/') if self.path == '' : fil.write(title_page) elif self.path == '/favicon.ico' : self.send_error(404, "No favicon.") elif self.path == '/nowebsite' : self.send_error(404, "No website.") elif len(pth) == 2 and pth[1] in collects : fil.write(collect_page(pth[1])) elif len(pth) == 3 and pth[1] in collects : origin = pth[1] + '/' + pth[2] if origin in port2pkg.keys() : fil.write(port_page(origin)) else: self.send_error(404, "No such port.") elif len(pth) == 6 : # Full path of a port description fil.write(print_desc(self.path)) else : self.send_error(404, "Non existing FreeBSD port or category.") return None fil.write("\n") length = fil.tell() fil.seek(0) self.send_response(200) self.send_header("Content-type", "text/html") self.send_header("Content-Length", str(length)) self.end_headers() return fil # And finally the routine which starts the http server, and instructs it to # answer tttp requests using the above RequestHandler. The http server itself # is borrowed from BaseHTTPServer. def run_server(HandlerClass = RequestHandler, ServerClass = BaseHTTPServer.HTTPServer, protocol="HTTP/1.0") : '''Modelled after the test() function of BaseHTTPServer. The first argument of the command will be the optional port number, otherwise 8080 is used.''' if sys.argv[1:]: tcpport = int(sys.argv[1]) else: tcpport = 8080 server_address = ('127.0.0.1', tcpport) HandlerClass.protocol_version = protocol httpd = ServerClass(server_address, HandlerClass) sockad = httpd.socket.getsockname() # Returns the IP address and the port number of the socket. print "Serving HTTP on", sockad[0], "port", sockad[1], "..." httpd.serve_forever() run_server()