#!/usr/local/bin/python "Shows the INDEX in the ports tree." # Author: Michel Talon # Version 1.0: Release, 14/12/2006 # Version 1.1: Html improvements by Mark Ovens, 18/12/2006 # Version 1.2: Makes use of wsgi, and fcgi, capitalizing on the example # http://hg.moinmo.in/moin/1.8/raw-file/tip/wiki/server/test.wsgi, 08/09/2008 # Licence: BSD (revised) # Date: September 2008 # Uses wsgi and flup, so that it can do fastcgi as a backend for apache or # lighttpd. Here is the required configuration for lighttpd: in lighttpd.conf # one needs mod_rewrite and mod_fastcgi, and one needs: # fastcgi.server = ( # ... # "/showindex.fcgi" => ( # "main" => ( # "socket" => "/tmp/showindex.sock", # "check-local" => "disable", # ) # ), # ... # ) # url.rewrite-once = ( # ... # "^(/showindex.*)$" => "/showindex.fcgi$1", # ... # ) # Finally point the browser at /showindex/ to trigger the fcgi server and get # the title page of FreeBSD ports. Please first install the flup port and # launch this script as root. Note that the presence of an INDEX in the ports # tree is required. import os, sys, re ######## Configuration ############### minspare = 1 # maxspare = 1 # maxthreads = 5 # socket = '/tmp/showindex.sock' # pidfile = '/var/run/showindex.pid' # wwwuid = 80 # wwwgid = 80 # ###################################### # 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.2" 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 += '''
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 = "/showindex/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. Using WSGI, the environment and # the function start_response are provided by the system. By hypothesis, all # requests are GET, with the location in the ports tree of a port. def request_handler(environ, start_response) : '''This sends the response code and MIME headers, plus content.''' # The request method and the path of the request are extracted from the # environment dictionary, filled by the wsgi system. If path is incorrect, # provide error codes, otherwise send the appropriate page as an iterator. status_b = "404 NOT FOUND" status_g = "200 OK" mime_headers = [('Content-Type','text/html'),] path = environ['PATH_INFO'] request_method = environ['REQUEST_METHOD'] if request_method == 'GET' : # Clean the leading /showindex/ path = path.replace('/showindex/','',1) path = path.rstrip('/') path = '/' + path pth = path.split('/') if path == '/' or path == '//showindex' : start_response(status_g,mime_headers) return [title_page] elif path == '/favicon.ico' : start_response(status_b,mime_headers) return [] elif path == '/nowebsite' : start_response(status_b,mime_headers) return ['

No website

'] elif len(pth) == 2 and pth[1] in collects : start_response(status_g,mime_headers) return [collect_page(pth[1])] elif len(pth) == 3 and pth[1] in collects : origin = pth[1] + '/' + pth[2] if origin in port2pkg.keys() : start_response(status_g,mime_headers) return [port_page(origin)] else : start_response(status_b,mime_headers) return ['

No such port

'] elif len(pth) == 6 : # Full path of a port description start_response(status_g,mime_headers) return [print_desc(path)] else : start_response(status_b,mime_headers) return ['

Non existing FreeBSD port or category.

'] else : start_response(status_b,mime_headers) return ['

Only GET requests are supported.

'] # The available "Hello World!" documentation is incorrect,one needs to set # options in the WSGIServer invocation. I have followed what Django does in # its fastcgi server. This becomes a very big process so get only one copy, # plus some threads. try: from flup.server.fcgi import WSGIServer except ImportError: print "Requires flup, see www/py-flup port." wsgi_opts = {'maxSpare' : maxspare, 'minSpare' : minspare, 'maxThreads' : maxthreads, 'bindAddress': socket} # Now become a daemon and make the socket accessible to the web browser pid = os.fork() if pid > 0 : sys.exit(0) # Exit original parent # In first child become independant os.chdir('/') # To not ober unmounts os.umask(0) os.setsid() # Become its own session leader pid = os.fork() if pid > 0 : # In first child import time time.sleep(3) while not os.access(socket, os.F_OK) : time.sleep(1) # the socket created by the child is made accessible to the web server os.chown(socket, wwwuid, wwwgid) os.chmod(socket, 0700) sys.exit(0) # Exit first child # In second child close stdin, stdout, stderror for i in range(3) : os.close(i) # and redirect stdout, stderror to /dev/null os.open('/dev/null', os.O_RDWR) # Becomes file descriptor 0 os.dup2(0,1) os.dup2(0,2) # Write the pid in '/var/run/showindex.pid' for use by rc-scripts pid = os.getpid() print pid fh = open(pidfile,'w') print >> fh, pid fh.close() os.chmod(pidfile,0444) os.chown(pidfile,wwwuid,wwwgid) # And finally the routine which starts the fcgi responder, as user www os.seteuid(wwwuid) WSGIServer(request_handler, **wsgi_opts).run() # Cleaning ... this seems to work after a signal killing the script. os.seteuid(0) os.unlink(socket) os.unlink(pidfile)
" + port2pkg[origin][0]\ + " " + port2pkg[origin][2] + "