<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-28134333</id><updated>2011-07-07T21:16:25.778+01:00</updated><category term='jython'/><category term='_csv'/><title type='text'>Gushie</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://gushieblog.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/28134333/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://gushieblog.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Paul Drummond</name><uri>http://www.blogger.com/profile/14865646552693571930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='29' src='http://3.bp.blogspot.com/_oZkvC9lhwfY/SQnXMfReE7I/AAAAAAAABbU/bw8I_X2dhAM/S220/me_face.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>6</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-28134333.post-2476009614073272208</id><published>2006-12-30T15:44:00.000Z</published><updated>2006-12-30T15:45:54.848Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='jython'/><category scheme='http://www.blogger.com/atom/ns#' term='_csv'/><title type='text'>Handling Method Arguments in Jython</title><content type='html'>I have been away from Jython for a while over the Christmas period but now I am back and eager to make some progress porting the &lt;code&gt;csv&lt;/code&gt; module.  So where was I?  Oh yes, last time I managed to get into a position where &lt;code&gt;test_csv.py&lt;/code&gt; ran successfully (success meaning that the test script executed - not that the tests passed!), so now I can start trying to get a test to pass.&lt;br /&gt;&lt;br /&gt;I have decided to tackle &lt;code&gt;test_reader_arg_valid1()&lt;/code&gt; test first simply because it's the first test in &lt;code&gt;test_csv.py&lt;/code&gt;:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def test_reader_arg_valid1(self):&lt;br /&gt; self.assertRaises(TypeError, csv.reader)&lt;br /&gt; self.assertRaises(TypeError, csv.reader, None)&lt;br /&gt; self.assertRaises(AttributeError, csv.reader, [], bad_attr = 0)&lt;br /&gt; self.assertRaises(csv.Error, csv.reader, [], 'foo')&lt;br /&gt; class BadClass:&lt;br /&gt;   def __init__(self):&lt;br /&gt;     raise IOError&lt;br /&gt; self.assertRaises(IOError, csv.reader, [], BadClass)&lt;br /&gt; self.assertRaises(TypeError, csv.reader, [], None)&lt;br /&gt; class BadDialect:&lt;br /&gt;   bad_attr = 0&lt;br /&gt; self.assertRaises(AttributeError, csv.reader, [], BadDialect)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;As you can see, a series of tests are being performed on the &lt;code&gt;csv.reader()&lt;/code&gt; method so I need to concentrate on implementing just enough of it to get the test to pass.&lt;br /&gt;&lt;br /&gt;From the python documentation, &lt;code&gt;csv.reader()&lt;/code&gt; is defined as follows:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;reader(csvfile[, dialect='excel'[, fmtparam]])&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;csvfile&lt;/code&gt; is the only required argument and it can be any object that supports the iterator protocol.  Next, the &lt;code&gt;dialect&lt;/code&gt; name can be specified as an optional parameter or omitted (in which case, the dialect will default to &lt;i&gt;excel&lt;/i&gt;).  The other optional &lt;code&gt;fmtparam&lt;/code&gt; keyword arguments can be given to override individual formatting parameters in the current dialect.&lt;br /&gt;&lt;br /&gt;So &lt;code&gt;csv.reader()&lt;/code&gt; has it all - mandatory, optional and keyword arguments.  I am going to need to figure out how this works in Jython to pass the &lt;code&gt;test_reader_arg_valid1()&lt;/code&gt; test.&lt;br /&gt;&lt;br /&gt;In Jython three types of method are supported:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;code&gt;&lt;b&gt;StandardCall&lt;/b&gt;&lt;/code&gt;: Mandatory, Positional arguments. (i.e. &lt;code&gt;void method(PyObject arg1, PyObject arg2) {} &lt;/code&gt;)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;code&gt;&lt;b&gt;PyArgsCall&lt;/b&gt;&lt;/code&gt;: List of optional, positional arguments. (i.e.  &lt;code&gt;void method(PyObject[] args)&lt;/code&gt;)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;code&gt;&lt;b&gt;PyArgsKeywordsCall&lt;/b&gt;&lt;/code&gt;: List of optional, positional or keyword arguments. (i.e. &lt;code&gt;void method(PyObject[] args, String[] keywords)&lt;/code&gt;)&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;As &lt;code&gt;csv.reader()&lt;/code&gt; must support keyword arguments, it must be defined as follows:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;public static void reader(PyObject[] args, String[] keywords) {&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;The parameter list must be specified exactly like this (except for the identifier names which can differ) because Jython uses reflection to make a method of type &lt;code&gt;PyArgsKeywordsCall&lt;/code&gt; only if it has exactly one &lt;code&gt;PyObject&lt;/code&gt; array as the first argument and one &lt;code&gt;String&lt;/code&gt; array as the second argument.  If you add another argument to the beginning of the parameter list, then the method will automatically be of &lt;code&gt;StandardCall&lt;/code&gt; type and won't support keyword arguments.&lt;br /&gt;&lt;br /&gt;I can use the handy helper class, &lt;code&gt;ArgParser&lt;/code&gt; to parse the arguments and extract the relevant values.  First, I need to create an instance of &lt;code&gt;ArgParser&lt;/code&gt; as follows:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;public static PyObject reader(PyObject[] args, String[] keywords) {&lt;br /&gt;&lt;br /&gt; ArgParser ap = new ArgParser(&lt;br /&gt;    "reader", &lt;br /&gt;    args, &lt;br /&gt;    keywords, &lt;br /&gt;    new String[] {&lt;br /&gt;      "csvfile", "dialect", "delimiter", &lt;br /&gt;      "doublequote", "escapechar", "lineterminator", &lt;br /&gt;      "quotechar", "quoting", "skipinitialspace" &lt;br /&gt;    });&lt;br /&gt; //..&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;code&gt;args&lt;/code&gt; and &lt;code&gt;keywords&lt;/code&gt; are passed into &lt;code&gt;ArgParser&lt;/code&gt; along with a list of the names of each argument that the method supports.  Then it is possible to simply pick out the value of a parameter by invoking a &lt;code&gt;getXXX()&lt;/code&gt; method specifying the position of the argument.  So to get the value of "quotechar", you'd ask for the value at position 6 as follows:&lt;br /&gt;&lt;code&gt;&lt;br /&gt; String quotechar = ap.getString(6, "'");&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;Simple, eh? It is equally as easy to support optional arguments.  For example, the &lt;code&gt;dialect&lt;/code&gt; argument would be extracted as follows:&lt;br /&gt;&lt;code&gt;&lt;br /&gt; String dialect = ap.getString(1, "excel");&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;Here, if dialect is not specified then it will default to "excel".&lt;br /&gt;&lt;br /&gt;Now that I have learned how to support positional, optional and keyword arguments in Jython I can focus on type checking the arguments and throwing the appropriate exceptions in order to pass &lt;code&gt;test_reader_arg_valid1()&lt;/code&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28134333-2476009614073272208?l=gushieblog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://gushieblog.blogspot.com/feeds/2476009614073272208/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=28134333&amp;postID=2476009614073272208' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/28134333/posts/default/2476009614073272208'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/28134333/posts/default/2476009614073272208'/><link rel='alternate' type='text/html' href='http://gushieblog.blogspot.com/2006/12/handling-method-arguments-in-jython.html' title='Handling Method Arguments in Jython'/><author><name>Paul Drummond</name><uri>http://www.blogger.com/profile/14865646552693571930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='29' src='http://3.bp.blogspot.com/_oZkvC9lhwfY/SQnXMfReE7I/AAAAAAAABbU/bw8I_X2dhAM/S220/me_face.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-28134333.post-4749585448940423928</id><published>2006-12-13T21:30:00.000Z</published><updated>2006-12-14T12:04:55.015Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='jython'/><category scheme='http://www.blogger.com/atom/ns#' term='_csv'/><title type='text'>Module Methods and Failing Tests</title><content type='html'>I have been looking at how CPython handles keyword arguments in methods today.  I've had my fair share of experience with Python over the years (though, not so much in the last few months) but I was totally unaware that methods may or may not support keyword arguments! Maybe that's because I often used the &lt;code&gt;PyQt&lt;/code&gt; GUI toolkit bindings which didn't support keywords arguments anyway, I'm not sure.&lt;br /&gt;&lt;br /&gt;At the end of my last entry my &lt;code&gt;_csv&lt;/code&gt; module was in a position where I was ready to implement the &lt;code&gt;register_dialect()&lt;/code&gt; method.  To do this I needed to figure out how Jython handles arguments as I thought I would need to support &lt;i&gt;keyword arguments&lt;/i&gt; for &lt;code&gt;register_dialect()&lt;/code&gt; - it's a python method after all and all python methods support keyword arguments don't they?  In fact, as it turns out this isn't always the case! Although not explicity mentioned in the documentation, some  CPython methods don't support keyword arguments and if you try to use them you will get a &lt;code&gt;TypeError&lt;/code&gt;.  Indeed, &lt;code&gt;csv.register_dialect()&lt;/code&gt; is one such method: &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Python 2.3.6 (#1, Nov 17 2006, 22:32:43)&lt;br /&gt;[GCC 4.1.2 20060928 (prerelease) (Ubuntu 4.1.1-13ubuntu5)] on linux2&lt;br /&gt;Type "help", "copyright", "credits" or "license" for more information.&lt;br /&gt;&gt;&gt;&gt; import csv&lt;br /&gt;&gt;&gt;&gt; csv.register_dialect(dialect=None, name="excel")&lt;br /&gt;Traceback (most recent call last):&lt;br /&gt;  File "&lt;stdin&gt;", line 1, in ?&lt;br /&gt;TypeError: register_dialect() takes no keyword arguments      &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Presumably &lt;code&gt;register_dialect()&lt;/code&gt; behaves like this because it is not &lt;i&gt;really&lt;/i&gt; a Python method.  The &lt;code&gt;csv.py&lt;/code&gt; module just exposes &lt;code&gt;register_dialect()&lt;/code&gt; from the C Module but a normal python developer would not know this and would quite rightly expect the method to support keyword arguments.  This inconsistency is less than ideal and it's tempting to fix it for Jython but I think that would be a mistake.  Jython is &lt;i&gt;supposed&lt;/i&gt; to mimic CPython's behaviour whether rightly or wrongly.  From a Jython perspective it's right if it's the way CPython behaves.&lt;br /&gt;&lt;br /&gt;So, in the case of &lt;code&gt;register_dialect()&lt;/code&gt; I can explicitly specify the arguments as follows:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;public static void register_dialect(PyObject name, PyObject dialect) {&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If I try to run &lt;code&gt;test_csv.py&lt;/code&gt; now I get the following error:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Traceback (innermost last):&lt;br /&gt;  File "dist/Lib/test/test_csv.py", line 9, in ?&lt;br /&gt;ImportError: no module named gc&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The &lt;code&gt;test_csv.py&lt;/code&gt; module uses the &lt;code&gt;gc&lt;/code&gt; module which isn't supported by Jython yet.  For now, I have just completely side-tracked this problem by making a copy of &lt;code&gt;test_csv.py&lt;/code&gt; and removing all the tests that involve &lt;code&gt;gc&lt;/code&gt;!  Problem solved (temporarily at least)!&lt;br /&gt;&lt;br /&gt;Now, when I run my own copy of &lt;code&gt;test_csv.py&lt;/code&gt; without the &lt;code&gt;gc&lt;/code&gt; calls I get yet another annoying error:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Traceback (innermost last):&lt;br /&gt;  File "test_csv.py", line 363, in ?&lt;br /&gt;  File "test_csv.py", line 364, in TestEscapedExcel&lt;br /&gt;  File "/jy/dist/Lib/csv.py", line 39, in __init__&lt;br /&gt;None: Dialect did not validate: quoting parameter not set&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This, and no doubt many other future cryptic errors are due to the fact that all the identifiers are the wrong type - they are all &lt;code&gt;PyObjects&lt;/code&gt; which confuses Jython a great deal.  Now is the right time to revisit each identifier and change it to the correct type.&lt;br /&gt;&lt;br /&gt;It's worth noting at this stage, my goal for today is to get &lt;code&gt;_csv&lt;/code&gt; into a state where it is good enough to fail all tests.  Wow, what a statement - lets say that again: &lt;b&gt;&lt;i&gt;I want _csv to be good enough to fail all tests!&lt;/b&gt;&lt;/i&gt;  What a strange goal to aim for.  Well, actually once I have &lt;code&gt;_csv&lt;/code&gt; in a state where &lt;code&gt;test_csv.py&lt;/code&gt; can properly execute I am in a far better position than I was before. I can analyse the output of &lt;code&gt;test_csv&lt;/code&gt; and tackle one test at a time, gaining satisfaction and confidence as I go.  This is one of the primarily advantages of &lt;a href="http://en.wikipedia.org/wiki/Test_driven_development"&gt;Test Driven Development&lt;/a&gt; and it's surprising how effective it is.&lt;br /&gt;&lt;br /&gt;First, I will tackle the methods.  Rather than figure out the parameters for each method I have simply specified &lt;code&gt;"PyObject[] args"&lt;/code&gt; as the parameter list which just means the method supports &lt;i&gt;0 or more arguments&lt;/i&gt;.  For example, I have implemented &lt;code&gt;unregister_dialect()&lt;/code&gt; as follows:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;static public PyObject unregister_dialect(PyObject[] args) {&lt;br /&gt;    return null;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;For all the &lt;code&gt;QUOTE_xxx&lt;/code&gt; identifiers I looked in the &lt;code&gt;_csv.c&lt;/code&gt; module and saw they were &lt;code&gt;enum&lt;/code&gt;s.  In Java I just make these separate integers to get them to work initially.  I left &lt;code&gt;Error&lt;/code&gt; as a &lt;code&gt;PyObject&lt;/code&gt; as I will need to spend some time looking at exceptions at a later date.  Similarly, I have left &lt;code&gt;Dialect&lt;/code&gt; well alone and will look into it when the time is right.  Finally, I changed &lt;code&gt;__doc__&lt;/code&gt; and &lt;code&gt;__version__&lt;/code&gt; to empty &lt;code&gt;String&lt;/code&gt;s to complete the process.&lt;br /&gt;&lt;br /&gt;With all the identifiers now the correct type, Jython is happy to run &lt;code&gt;test_csv.py&lt;/code&gt;.  Of course, not many tests pass and there is a lot of output - here's a sample of it:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;======================================================================&lt;br /&gt;FAIL: test_reader_arg_valid1 (__main__.Test_Csv)&lt;br /&gt;----------------------------------------------------------------------&lt;br /&gt;Traceback (most recent call last):&lt;br /&gt;  File "/jy/dist/Lib/unittest.py", line 229, in __call__&lt;br /&gt;  File "test_csv.py", line 19, in test_reader_arg_valid1&lt;br /&gt;  File "/jy/dist/Lib/unittest.py", line 295, in failUnlessRaises&lt;br /&gt;AssertionError: TypeError&lt;br /&gt;----------------------------------------------------------------------&lt;br /&gt;Ran 65 tests in 0.666s&lt;br /&gt;&lt;br /&gt;FAILED (failures=4, errors=55)&lt;br /&gt;Traceback (innermost last):&lt;br /&gt;  File "test_csv.py", line 716, in ?&lt;br /&gt;  File "test_csv.py", line 0, in test_main&lt;br /&gt;  File "/jy/dist/Lib/test/test_support.py", line 262, in run_unittest&lt;br /&gt;  File "/jy/dist/Lib/test/test_support.py", line 246, in run_suite&lt;br /&gt;TestFailed: errors occurred; run in verbose mode for details&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I may have 59 failures but this is a much better position than before. I now have something to focus on - I can tackle each test as it comes and gain confidence as the number of failures decrease and the number of passes increase until the porting process is complete. Yippee!&lt;br /&gt;&lt;br /&gt;Now I am ready to implement the module proper, the first task is to find out what "c.s.v" stands for! ;) :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28134333-4749585448940423928?l=gushieblog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://gushieblog.blogspot.com/feeds/4749585448940423928/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=28134333&amp;postID=4749585448940423928' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/28134333/posts/default/4749585448940423928'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/28134333/posts/default/4749585448940423928'/><link rel='alternate' type='text/html' href='http://gushieblog.blogspot.com/2006/12/method-arguments.html' title='Module Methods and Failing Tests'/><author><name>Paul Drummond</name><uri>http://www.blogger.com/profile/14865646552693571930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='29' src='http://3.bp.blogspot.com/_oZkvC9lhwfY/SQnXMfReE7I/AAAAAAAABbU/bw8I_X2dhAM/S220/me_face.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-28134333.post-8258251556825036485</id><published>2006-12-13T20:41:00.000Z</published><updated>2006-12-13T21:20:56.571Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='jython'/><category scheme='http://www.blogger.com/atom/ns#' term='_csv'/><title type='text'>Porting C Modules to Jython</title><content type='html'>CPython includes many library modules, some of which are written in pure Python (which is great because these will work in Jython (hopefully) without modification), but others are written in C, which means they must be rewritten in Java in order to work with Jython.  Take the &lt;code&gt;csv&lt;/code&gt; module for example.  It is a Python library module so it should be possible to use it in Jython as-is without any extra work.  If only it were that that simple!  You see, if you look at the code for &lt;code&gt;csv.py&lt;/code&gt; you'll notice it uses another module called &lt;code&gt;_csv&lt;/code&gt; for most of it's behaviour and it just so happens that &lt;code&gt;_csv&lt;/code&gt; is written in C.  Therefore, it is necessary to &lt;i&gt;port&lt;/i&gt; this module to Jython. It's worth noting that there are many cases where a python library module is just a wrapper for an underlying C module, but not always - for example, &lt;code&gt;cStringIO&lt;/code&gt; and &lt;code&gt;cPickle&lt;/code&gt; are first-class library modules implemented in C.&lt;br /&gt;&lt;br /&gt;Before actually creating the &lt;code&gt;_csv&lt;/code&gt; module, it's always helpful to see things fail first then you get a nice feeling of satisfaction when you get the test to pass (or fail less!).  So to prove that &lt;code&gt;csv&lt;/code&gt; doesn't work in current Jython builds I ran the following test:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;bash# jython dist/Lib/test/test_csv.py&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;which resulted in the following predictable error:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Traceback (innermost last):&lt;br /&gt;  File "dist/Lib/test/test_csv.py", line 8, in ?&lt;br /&gt;  File "/work/jython/dist/Lib/csv.py", line 7, in ?&lt;br /&gt;ImportError: no module named _csv&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;As expected &lt;code&gt;csv.py&lt;/code&gt; is unable to import the &lt;code&gt;_csv&lt;/code&gt; module because it doesn't exist.  To create it I followed the guidelines by &lt;i&gt;Charlie Groves&lt;/i&gt; in the &lt;a href="http://wiki.python.org/jython/JythonDeveloperGuide/PortingPythonModulesToJython"&gt;wiki&lt;/a&gt; (which - funnily enough - uses the &lt;code&gt;csv&lt;/code&gt; module as an example - what a coincidence!).   I created a &lt;code&gt;_csv.java&lt;/code&gt; file in &lt;code&gt;$JYTHON_HOME/src/org/python/modules&lt;/code&gt;" then added &lt;code&gt;"_csv"&lt;/code&gt; to the list of modules in &lt;code&gt;Setup.java&lt;/code&gt;.  After building Jython and running &lt;code&gt;test_csv.py&lt;/code&gt; again I saw the following error:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Traceback (innermost last):&lt;br /&gt;  File "dist/Lib/test/test_csv.py", line 8, in ?&lt;br /&gt;  File "/work/jython/dist/Lib/csv.py", line 7, in ?&lt;br /&gt;ImportError: cannot import names Error, __version__, &lt;br /&gt;  writer, reader, register_dialect, unregister_dialect, &lt;br /&gt;  get_dialect, list_dialects, QUOTE_MINIMAL, QUOTE_ALL, &lt;br /&gt;  QUOTE_NONNUMERIC, QUOTE_NONE, __doc__&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now, I have a different error, but the fact that the first error has disappeared means that Jython has recognised my new &lt;code&gt;_csv&lt;/code&gt; module!  The new error is just Jython complaining because &lt;code&gt;_csv&lt;/code&gt; doesn't define any of the identifiers that it is expecting.&lt;br /&gt;&lt;br /&gt;Some of the missing identifiers are simple to resolve, like &lt;code&gt;__doc__&lt;/code&gt; which is just a string.  Others are more difficult and will require further investigation like &lt;code&gt;Error&lt;/code&gt; which is an exception and I don't know how to do exceptions in Jython yet.  For now, I will just add everything as a &lt;code&gt;PyObject&lt;/code&gt; to get past the error, then I will revisit each in turn.  Here's &lt;code&gt;_csv.java&lt;/code&gt; as it looks after adding all the missing identifiers:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;public class _csv {&lt;br /&gt;    public static PyObject Error;&lt;br /&gt;    public static String __version__ = "1.0";&lt;br /&gt;    public static PyObject Dialect;&lt;br /&gt;    public static PyObject writer;&lt;br /&gt;    public static PyObject reader;&lt;br /&gt;    public static PyObject register_dialect;&lt;br /&gt;    public static PyObject unregister_dialect;&lt;br /&gt;    public static PyObject get_dialect;&lt;br /&gt;    public static PyObject list_dialects;&lt;br /&gt;    public static PyObject QUOTE_MINIMAL;&lt;br /&gt;    public static PyObject QUOTE_ALL;&lt;br /&gt;    public static PyObject QUOTE_NONNUMERIC;&lt;br /&gt;    public static PyObject QUOTE_NONE;&lt;br /&gt;    public static String __doc__;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now, when I run &lt;code&gt;test_csv.py&lt;/code&gt; I get the following error:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Traceback (innermost last):&lt;br /&gt;  File "dist/Lib/test/test_csv.py", line 8, in ?&lt;br /&gt;  File "/work/jython/dist/Lib/csv.py", line 87, in ?&lt;br /&gt;TypeError: call of non-function ('NoneType' object)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Although the error isn't very helpful, I can go to line 87 in &lt;code&gt;csv.py&lt;/code&gt; (just by clicking on the error in Eclipse) and see that Jython is unhappy because &lt;code&gt;register_dialect&lt;/code&gt; is supposed to be a method yet I have defined it as a &lt;code&gt;PyObject&lt;/code&gt;, so now I can forget about the other identifiers and focus on getting this method to work.  &lt;br /&gt;&lt;br /&gt;This is where this entry ends while I go and figure out how methods and dynamic arguments work in Jython!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28134333-8258251556825036485?l=gushieblog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://gushieblog.blogspot.com/feeds/8258251556825036485/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=28134333&amp;postID=8258251556825036485' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/28134333/posts/default/8258251556825036485'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/28134333/posts/default/8258251556825036485'/><link rel='alternate' type='text/html' href='http://gushieblog.blogspot.com/2006/12/porting-c-modules-to-jython.html' title='Porting C Modules to Jython'/><author><name>Paul Drummond</name><uri>http://www.blogger.com/profile/14865646552693571930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='29' src='http://3.bp.blogspot.com/_oZkvC9lhwfY/SQnXMfReE7I/AAAAAAAABbU/bw8I_X2dhAM/S220/me_face.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-28134333.post-359218969679528704</id><published>2006-11-20T17:53:00.000Z</published><updated>2006-11-20T21:30:52.053Z</updated><title type='text'>Trac: More Than Just a Bug Tracker</title><content type='html'>&lt;span style="font-weight: bold;font-size:100%;" &gt;Introducing Trac&lt;/span&gt;&lt;br /&gt;Trac is an enhanced wiki and issue tracking system for software development projects.&lt;br /&gt;&lt;div style="text-align: center;"&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://photos1.blogger.com/x/blogger2/3005/3434/1600/906121/navbar.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://photos1.blogger.com/x/blogger2/3005/3434/320/738627/navbar.png" alt="" border="0" /&gt;&lt;/a&gt;&lt;span style="font-style: italic;font-size:78%;" &gt;&lt;span style="font-weight: bold;"&gt;Note: Click each image to see full size&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;Trac uses a minimalistic approach to web-based software project management. As you can see from the navigation bar above Trac includes a wiki and a bug tracker (where bugs and tasks are referred to as &lt;span style="font-style: italic;"&gt;tickets&lt;/span&gt;), as well as other less obvious features:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-style: italic;"&gt;Timeline&lt;/span&gt; - lists all Trac events that have occurred in chronological order, a brief description of each event and if applicable, the person responsible for the change.&lt;/li&gt;&lt;li&gt;&lt;span style="font-style: italic;"&gt;Roadmap&lt;/span&gt; - provides a view on the ticket system that helps planning and managing the future development of a project.&lt;/li&gt;&lt;li&gt;&lt;span style="font-style: italic;"&gt;Browse Source - &lt;/span&gt;Trac is fully integrated with Subversion - more on this later!&lt;/li&gt;&lt;li&gt;&lt;span style="font-style: italic;"&gt;Lots More!&lt;/span&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:100%;" &gt;Creating a New Ticket&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size:100%;"&gt;Entering a new ticket is simple.  Just select the type, enter a description, select the relevant properties then hit "Submit Ticket".&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;div style="text-align: center;"&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://photos1.blogger.com/x/blogger2/3005/3434/1600/974979/new_ticket.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://photos1.blogger.com/x/blogger2/3005/3434/200/833383/new_ticket.png" alt="" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;div style="text-align: left;"&gt;One of the major advantages of Trac is that it's extremely easy to add new fields to the ticket system.&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;span style="font-size:100%;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-weight: bold;font-size:100%;" &gt;Integrated Wiki&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size:100%;"&gt;A fully featured wiki is integrated into the Trac system with fully history and diff&lt;/span&gt;&lt;span style="font-size:100%;"&gt; support.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://photos1.blogger.com/x/blogger2/3005/3434/1600/358347/wiki_history.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://photos1.blogger.com/x/blogger2/3005/3434/200/115086/wiki_history.png" alt="" border="0" /&gt;&lt;/a&gt;&lt;span style="font-weight: bold;font-size:100%;" &gt;Queries&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size:100%;"&gt;Searches and queries can be done through the SQL-style reports or the more user-friendly "Custom Query" screen show here.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://photos1.blogger.com/x/blogger2/3005/3434/1600/230874/query.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://photos1.blogger.com/x/blogger2/3005/3434/320/615463/query.png" alt="" border="0" /&gt;&lt;/a&gt;&lt;span style="font-size:100%;"&gt;The query interface supports custom fields and the results can be sorted by column.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-weight: bold;font-size:100%;" &gt;Project Management&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size:100%;"&gt;The roadmap section provides an interactive graphical overview of progress for&lt;/span&gt;&lt;span style="font-size:100%;"&gt; each milestone.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://photos1.blogger.com/x/blogger2/3005/3434/1600/366017/roadmap1.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://photos1.blogger.com/x/blogger2/3005/3434/320/747258/roadmap1.png" alt="" border="0" /&gt;&lt;/a&gt;&lt;span style="font-size:100%;"&gt;Clicking on the filled part of the bar takes you to a query showing all completed tickets and clicking on the empty part shows all active tickets.&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:100%;" &gt;Milestones&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://photos1.blogger.com/x/blogger2/3005/3434/1600/799204/milestone2.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://photos1.blogger.com/x/blogger2/3005/3434/320/45232/milestone2.png" alt="" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Clicking on a particular milestone from the Roadmap will take you to a detailed view showing more statistics, this time for various different properties.    You can view tickets by owner, severity, etc.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://photos1.blogger.com/x/blogger2/3005/3434/1600/776923/milestone1.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://photos1.blogger.com/x/blogger2/3005/3434/320/729859/milestone1.png" alt="" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:100%;" &gt;Timeline&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size:100%;"&gt;The timeline page shows a chronological list of all events and is a good way to see what's change since your last visit.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://photos1.blogger.com/x/blogger2/3005/3434/1600/726661/timeline.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://photos1.blogger.com/x/blogger2/3005/3434/320/838798/timeline.png" alt="" border="0" /&gt;&lt;/a&gt;&lt;span style="font-size:100%;"&gt;&lt;br /&gt;It includes all sorts of interesting events from wiki changes to subversion commits to milestone completions.  Each event provides a link to more detailed information. For example, an svn commit links directly to a visual diff of the changeset, which neatly brings me onto the next feature...&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:100%;" &gt;Changesets&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size:100%;"&gt;Trac is extremely well integrated with Subversion and provides a nifty diff viewer.  Show here is the in-line viewer but you can alter it to show changes side-by-side.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://photos1.blogger.com/x/blogger2/3005/3434/1600/831553/changeset.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://photos1.blogger.com/x/blogger2/3005/3434/320/858481/changeset.png" alt="" border="0" /&gt;&lt;/a&gt;&lt;span style="font-size:100%;"&gt;&lt;br /&gt;Diffs aren't limited to the previous change - you can do a diff on any revision in the repository as illustrated in the next section.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:100%;" &gt;Source Browser&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://photos1.blogger.com/x/blogger2/3005/3434/1600/565517/revision_log.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://photos1.blogger.com/x/blogger2/3005/3434/320/11706/revision_log.png" alt="" border="0" /&gt;&lt;/a&gt;&lt;span style="font-size:100%;"&gt;&lt;br /&gt;The source browser lists all the changes in the repository and allows you to compare any two revisions - very powerful!&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:100%;" &gt;Links, Links, Links!&lt;/span&gt;&lt;br /&gt;Trac provides extensive support for linking to various events and items within the system for both wiki pages and ticket comments.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://photos1.blogger.com/x/blogger2/3005/3434/1600/301067/traclinks.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://photos1.blogger.com/x/blogger2/3005/3434/320/201986/traclinks.png" alt="" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Linking to source code changes is particularly powerful.  When a fix is detected for a particular bug, the developer can easily link to the changeset from the ticket allowing readers to jump to a diff showing exactly what changes were required to fix the bug.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:100%;" &gt;Inline Diffs&lt;/span&gt;&lt;br /&gt;If linking to a particular changeset isn't immediate enough for you, then why not display the diff directly in the wiki page or ticket comment?&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://photos1.blogger.com/x/blogger2/3005/3434/1600/504892/inline-diff.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://photos1.blogger.com/x/blogger2/3005/3434/320/480388/inline-diff.png" alt="" border="0" /&gt;&lt;/a&gt;&lt;span style="font-weight: bold;font-size:100%;" &gt;&lt;br /&gt;Summary&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size:100%;"&gt;For more information on Trac refer to the &lt;a href="http://trac.edgewall.org/"&gt;website.&lt;/a&gt;  There is also a &lt;a href="http://www.hosted-projects.com/trac/TracDemo/Demo"&gt;demo&lt;/a&gt; project that you can checkout to evaluate and play around with Trac before downloading.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28134333-359218969679528704?l=gushieblog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://gushieblog.blogspot.com/feeds/359218969679528704/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=28134333&amp;postID=359218969679528704' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/28134333/posts/default/359218969679528704'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/28134333/posts/default/359218969679528704'/><link rel='alternate' type='text/html' href='http://gushieblog.blogspot.com/2006/11/trac.html' title='Trac: More Than Just a Bug Tracker'/><author><name>Paul Drummond</name><uri>http://www.blogger.com/profile/14865646552693571930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='29' src='http://3.bp.blogspot.com/_oZkvC9lhwfY/SQnXMfReE7I/AAAAAAAABbU/bw8I_X2dhAM/S220/me_face.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-28134333.post-115015404915358807</id><published>2006-06-13T00:11:00.000+01:00</published><updated>2006-06-18T19:36:41.260+01:00</updated><title type='text'>Doxygen Versus Javadoc</title><content type='html'>As a C++ programmer accustomed to &lt;a href="http://www.doxygen.org"&gt;Doxygen&lt;/a&gt; I was always curious to learn a language where automatic code generation was taken seriously and supported as standard.   When I finally moved over to a Java project I was shocked to discover how obtrusive and "in your face" Javadoc is.   &lt;i&gt;&lt;b&gt;It seems to go out of it's way to get in my way!&lt;/b&gt;&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;By comparison Doxygen is about as good as it gets.  It is designed to produce great looking documentation with the least amount of developer effort.   Javadoc, on the other hand expects developer contribution in areas that I feel are perfect candidates for automation.  Take paragraphs for example; Javadoc expects the developer to use the standard HTML paragraph tag &amp;lt;p&amp;gt; in the comments.  Why? Why? Why?  Surely it would be quite simple to automatically detect an empty line as the start of a new paragraph?&lt;br /&gt;&lt;br /&gt;Many Java developers - including the Javadoc development team I'm sure - would take the view that HTML is the obvious choice for Javadoc text formatting and I agree that, at least theoretically it seems an obvious choice.  In practice however, there is simply no need for HTML for simple text formatting such as marking text as bold, italic, etc.  Using HTML for anything else is overkill in a source comment and only serves to make the comment unreadable in source form.&lt;br /&gt;&lt;br /&gt;Examine the following Javadoc comment:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;/**&lt;br /&gt;  This is &amp;lt;i&amp;gt;the&amp;lt;/i&amp;gt; Rectangle class.  &lt;br /&gt;  &amp;lt;p&amp;gt; &lt;br /&gt;  Refer to &amp;lt;a href="./doc-files/shapes-overview.html"&amp;gt;&lt;br /&gt;  shape-overview&amp;lt;/a&amp;gt; for more details.&lt;br /&gt;  &amp;lt;p&amp;gt;&lt;br /&gt;  There are four types of supported {@link Shape}:&lt;br /&gt;  &amp;lt;ul&amp;gt;&lt;br /&gt;    &amp;lt;li&amp;gt;{@link Rectangle} (this class)&amp;lt;/li&amp;gt;&lt;br /&gt;    &amp;lt;li&amp;gt;{@link Circle}&amp;lt;/li&amp;gt;&lt;br /&gt;    &amp;lt;li&amp;gt;{@link Square}&amp;lt;/li&amp;gt;&lt;br /&gt;    &amp;lt;li&amp;gt;{@link Triangle}&amp;lt;/li&amp;gt;&lt;br /&gt;  &amp;lt;/ul&amp;gt;&lt;br /&gt;*/&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here is the equivalent comment using Doxygen:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;/**&lt;br /&gt;  This is &amp;lt;i&amp;gt;the&amp;lt;/i&amp;gt; Rectangle class.&lt;br /&gt;&lt;p&gt;&lt;br /&gt;  Refer to \ref shape-overview for more details.&lt;br /&gt;&lt;p&gt;&lt;br /&gt;  There are four types of supported Shape:&lt;br /&gt;    - Rectangle (this class)&lt;br /&gt;    - Circle&lt;br /&gt;    - Square&lt;br /&gt;    - Triangle&lt;br /&gt;*/&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Points to note in this comparison are:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt; &lt;li&gt;Doxygen will automatically recognise all code objects and insert a hyperlink, hence there is no need for a @link tag.&lt;/li&gt; &lt;br /&gt; &lt;li&gt;Doxygen provides a very convenient shorthand notation for lists.&lt;/li&gt;&lt;br /&gt; &lt;li&gt;Notice how easy it is to reference another page in the documentation compared to Javadoc's use of the HTML HREF tag.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;The most important problem with the Javadoc comment in the comparison is how much I need to concentrate on formatting issues while writing it.  When writing Javadoc I am constantly thinking about what should and shouldn't be linked, whether the list will look right, etc.  This is frustrating because, while I &lt;i&gt;&lt;b&gt;do&lt;/b&gt;&lt;/i&gt; want to document my code well I also want to focus on coding.  Therefore, due to the effort involved in commenting Javadoc-style, I usually focus on the code while in a heavy development session then I go through and document everything afterwards.  I'd much rather document my code incrementally during development, but Javadoc, it seems,  almost strives to make this as difficult as possible! Doxygen allows me to use HTML where it works well (marking text as bold, etc.) but also supports convenient shorthand for lists and is intelligent enough to realise that an empty line should be converted into the start of a new paragraph in the generated documentation.&lt;br /&gt;&lt;br /&gt;I have provided the generated documentation of the Shape example for both &lt;a href="http://www.paul.drummond.dsl.pipex.com/gushie-blog/jdoc_v_dox/shapesDoxygen/index.html"&gt;Doxygen&lt;/a&gt;  and &lt;a href="http://www.paul.drummond.dsl.pipex.com/gushie-blog/jdoc_v_dox/shapesJavadoc/index.html"&gt;Javadoc&lt;/a&gt;  so you can decide for yourself which approach you prefer. Note the following Doxygen features:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt; &lt;li&gt; Doxygen provides a hyperlinked graphical class hierarchy although it is initially well hidden! From the main page, select the "Classes" tab, then the "Class Hierarchy" sub-tab then click "Goto the graphical hierarchy".&lt;br /&gt;&lt;br /&gt; &lt;li&gt; Doxygen will produce a hyperlinked graphical class hierarchy for every class at  the top of the page.&lt;/li&gt;&lt;br /&gt;&lt;br /&gt; &lt;li&gt; In the Doxygen page for Rectangle, notice that there is a link to the source code.  Doxygen generates a hyperlinked HTML source browser for all source code.&lt;/li&gt;&lt;br /&gt;&lt;br /&gt; &lt;li&gt; Doxygen has a "\todo" command and will automatically generated a hyperlinked todo page.  Handy!  It supports a bug list and test list.&lt;/li&gt;&lt;br /&gt;&lt;br /&gt; &lt;li&gt;Doxygen provides many more features including full support for graphical class charts, grouping of classes, mathematical formulas, multiple output formats (HTML, LATEX, PDF, man pages, HTMLHelp, etc.).&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;So what do &lt;i&gt;you&lt;/i&gt; think?  If you are a Java developer are you surprised how powerful Doxygen is or do you feel that the Javadoc approach is better?  I'd be interested to here from developers who really prefer the Javadoc approach as it baffles me, that's for sure!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28134333-115015404915358807?l=gushieblog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://gushieblog.blogspot.com/feeds/115015404915358807/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=28134333&amp;postID=115015404915358807' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/28134333/posts/default/115015404915358807'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/28134333/posts/default/115015404915358807'/><link rel='alternate' type='text/html' href='http://gushieblog.blogspot.com/2006/06/doxygen-versus-javadoc.html' title='Doxygen Versus Javadoc'/><author><name>Paul Drummond</name><uri>http://www.blogger.com/profile/14865646552693571930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='29' src='http://3.bp.blogspot.com/_oZkvC9lhwfY/SQnXMfReE7I/AAAAAAAABbU/bw8I_X2dhAM/S220/me_face.jpg'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-28134333.post-114781349353655685</id><published>2006-05-16T22:04:00.000+01:00</published><updated>2006-05-16T22:05:08.463+01:00</updated><title type='text'>Vim7.0 Released</title><content type='html'>A new and much improved version of my favourite editor was released last week (I would've posted earlier but my blog didn't exist then!). It includes handy features such as tabbed windows, visual on-the-fly spell checking and an intellisense/auto-complete feature called "omni-completion".&lt;br /&gt;&lt;br /&gt;I have played with it a little but haven't been able to get the omni-completion working for Java yet. I will figure it out when I have more time, maybe later in the week.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28134333-114781349353655685?l=gushieblog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://gushieblog.blogspot.com/feeds/114781349353655685/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=28134333&amp;postID=114781349353655685' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/28134333/posts/default/114781349353655685'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/28134333/posts/default/114781349353655685'/><link rel='alternate' type='text/html' href='http://gushieblog.blogspot.com/2006/05/vim70-released_16.html' title='Vim7.0 Released'/><author><name>Paul Drummond</name><uri>http://www.blogger.com/profile/14865646552693571930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='29' src='http://3.bp.blogspot.com/_oZkvC9lhwfY/SQnXMfReE7I/AAAAAAAABbU/bw8I_X2dhAM/S220/me_face.jpg'/></author><thr:total>1</thr:total></entry></feed>
