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="http://trac.edgewall.com/" mce_href="http://trac.edgewall.com/" ><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 var:header_logo.link ?>"><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 var:header_logo.link
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/db_default.py: ('header_logo', 'link', 'http://trac.edgewall.com/'), ./trac/db_default.py: ('header_logo', 'src', 'trac_banner.png'), ./trac/db_default.py: ('header_logo', 'alt', 'Trac'), ./trac/db_default.py: ('header_logo', 'width', '236'), ./trac/db_default.py: ('header_logo', 'height', '73'), ./trac/web/chrome.py: logo_src = self.config.get('header_logo', 'src') ./trac/web/chrome.py: req.hdf['header_logo'] = { ./trac/web/chrome.py: 'link': self.config.get('header_logo', 'link'), ./trac/web/chrome.py: 'alt': escape(self.config.get('header_logo', 'alt')), ./trac/web/chrome.py: 'width': self.config.get('header_logo', 'width'), ./trac/web/chrome.py: 'height': self.config.get('header_logo', 'height')
The first set of hits, from db_default.py
, are from the code that builds up a tuple of tuples called default_config
:
default_config = (('trac', 'htdocs_location', '/trac/'), ... ('header_logo', 'link', 'http://trac.edgewall.com/'), ('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 chrome.py
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, chrome.py
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 env.py
:
def insert_default_data(self): ... for section,name,value in db_default.default_config: self.config.set(section, name, value) self.config.save()
and:
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.