Navigating Source

One of the minor items on my to-do list is to replace the default Trac graphics on the Argon web site with ones that include some reference to the University of Toronto and our hippo mascot. Having spent half an hour on Friday talking to a colleague about how we teach students how to write code, but not how to read it, I thought I'd describe how I figured out what files I'd need to change, and where to find them. Step 1: what am I looking for? If you take a look at the Argon site, you'll see two logos: a large banner at the top of the page, and a smaller "Trac Powered" logo at the bottom. "View Source" shows me that the top one is produced by the following HTML:
<div id="header"> <a id="logo" xhref="" mce_href="" ><img xsrc="/trac-static/trac_banner.png" mce_src="/trac-static/trac_banner.png"  width="236" height="73" alt="Trac" /></a> <hr /> </div>
The footer is similar. Step 2: That <div id="header"> tag looks like a good landmark. I find it like this:
$ cd ~/argon/trunk/trac $ fgrep -e 'id="header"' templates/*.cs templates/header.cs:
Notice that I'm relying here on what I've already learned about Trac: I know that the Python code generates pages based on ClearSilver templates, which live in the templates directory, and have a .cs extension. If I didn't know that, I'd have run this command instead:
$ cd ~/argon/trunk/trac $ fgrep -e 'id="header"' `find . -type f -print | fgrep -v -e '.svn'`
(I pipe the results of find through fgrep to filter out everything in the .svn directories that Subversion uses to manage metadata.) Step 3: take a closer look at the contents of header.cs:
<div id="header"> <a id="logo" xhref="<?cs ?>"><img xsrc="<?cs var:header_logo.src ?>" width="<?cs var:header_logo.width ?>" height="<?cs var:header_logo.height ?>" alt="<?cs var:header_logo.alt ?>" /></a> <hr /> </div>
The notation tells ClearSilver to look in the HDF (Hierarchical Data Format) structure that the Python CGI builds up for it, find the header_logo key, and use the value associated with its link subkey. Do another search:
$ fgrep header_logo `find . -name '*.py' -print` ./trac/  ('header_logo', 'link', ''), ./trac/  ('header_logo', 'src', 'trac_banner.png'), ./trac/  ('header_logo', 'alt', 'Trac'), ./trac/  ('header_logo', 'width', '236'), ./trac/  ('header_logo', 'height', '73'), ./trac/web/        logo_src = self.config.get('header_logo', 'src') ./trac/web/        req.hdf['header_logo'] = { ./trac/web/            'link': self.config.get('header_logo', 'link'), ./trac/web/            'alt': escape(self.config.get('header_logo', 'alt')), ./trac/web/            'width': self.config.get('header_logo', 'width'), ./trac/web/            'height': self.config.get('header_logo', 'height')
The first set of hits, from, are from the code that builds up a tuple of tuples called default_config:
default_config = (('trac', 'htdocs_location', '/trac/'), ... ('header_logo', 'link', ''), ('header_logo', 'src', 'trac_banner.png'), ('header_logo', 'alt', 'Trac'), ('header_logo', 'width', '236'), ('header_logo', 'height', '73'), ... )
Make a note of that, then look in at the second set of hits, which come from the following block of code:
logo_src = self.config.get('header_logo', 'src') logo_src_abs = logo_src.startswith('http://') or logo_src.startswith('https://') if not logo_src[0] == '/' and not logo_src_abs: logo_src = htdocs_location + logo_src req.hdf['header_logo'] = { 'link': self.config.get('header_logo', 'link'), 'alt': escape(self.config.get('header_logo', 'alt')), 'src': logo_src, 'src_abs': logo_src_abs, 'width': self.config.get('header_logo', 'width'), 'height': self.config.get('header_logo', 'height') }
OK, is looking in the configuration object that Trac creates each time it services a request, pulling out the values that describe the header logo, and sticking them into the HDF for ClearSilver to use. That leaves two questions: how do values get from db_default.default_config into the configuration object, and where do files like trac_banner.png actually live on disk? Step 4: grepping for default_config gets two hits in
def insert_default_data(self): ... for section,name,value in db_default.default_config: self.config.set(section, name, value)
def load_config(self): self.config = Configuration(os.path.join(self.path, 'conf', 'trac.ini')) for section,name,value in db_default.default_config: self.config.setdefault(section, name, value)
Ah ha! The configuration object initializes itself by reading from trac.ini. All we have to do now is find that... Step 5: Our Trac is run by Apache 2 on Debian Linux, which keeps configuration information under the /etc/apache2/conf.d directory. Sure enough, there's a trac file in conf.d, which contains the following lines:
Alias /trac-static/ "/usr/share/trac/htdocs/" <Directory "/usr/share/trac/htdocs/"> Options Indexes MultiViews AllowOverride None Order allow,deny Allow from all </Directory>  <Directory "/usr/share/trac/cgi-bin"> AllowOverride None Options ExecCGI -MultiViews +SymLinksIfOwnerMatch AddHandler cgi-script .cgi  Order allow,deny Allow from all </Directory>  <LocationMatch "/trac/[[:alnum:]]+/login"> AuthPAM_Enabled on AuthName "Pyre Trac" AuthType Basic require valid-user </LocationMatch>
Remember looking at the HTML source in Step 1? The image path specified there was /trac-static/trac_banner.png, so only the first stanza matters right now. It specifies that /trac-static is translated into /usr/share/trac/htdocs, and sure enough, that directory contains the PNG images for the two logos. It also contains a trac.ico file, which produces the little pawprint logo next to Trac URLs in the browser and in Favorites links. And that's that---the artwork still needs to be done, but at least I know where to put files for testing. Now, you'll probably never need to find Trac logo files on your computer, but every time you start work with a new code base, you'll need to find your way around. If there isn't a comprehensive, up-to-date map of the code (and there never is), the first thing to do is to find a landmark, like the div element I picked in Step 1. Work backward from that: who touches it? Where does that code get its inputs? Keep notes---I had half a page of them by the time I was done---so that when you hit a roadblock, you can back up and try another path. Anything you already know about the application's architecture will help steer you in the right direction, so make sure you understand its processing cycle. And above all, don't panic, and don't be afraid to ask for help.
comments powered by Disqus