Ditz versus bugs everywhere

A few months ago, I sketched out a ticket-tracking system that would be married with my source code. Then some commenters told me about bugs everywhere (be) and ditz.

I’ve looked at both, but I’ve been using ditz full-time while just watching be. Anyhow, here’s a few comparisons:

setting up a project

Here’s what it looks like when you set up a project in ditz:

$ ditz init
I wasn't able to find a configuration file ./.ditz-config.
We'll set it up right now.
Your name (enter for Matthew Wilson):
Your email address (enter for [email protected]):
Directory to store issues state in (enter for .ditz):
Use your text editor for multi-line input when possible (y/n)? y
Paginate output (always/never/auto)? auto
Project name (enter for scratch):
Issues can be tracked across the project as a whole, or the project can be
split into components, and issues tracked separately for each component.
Track issues separately for different components? (y/n): y

Current components:

(A)dd component, (r)emove component, or (d)one: a
Component name: documentation

... snip ...

(A)dd component, (r)emove component, or (d)one: d
Ok, .ditz directory created successfully.

And here’s how you can create a single issue.

$ ditz add
Title: Write something justifying yet another web framework
Is this a (b)ugfix, a (f)eature, or a (t)ask? t
Choose a component:
1) scratch
2) documentation
3) model code
4) controller code
5) view code
Component (1--5): 2
Issue creator (enter for Matthew Wilson ):
Added issue documentation-1 (e8a4a43f78ee83300cc0372a13375d9534b97abb).

You can’t tell, but when I punched in the title, ditz opened my $EDITOR and I wrote a longer description in there.

Now the same thing in be:

$ be set-root
Guessing id 'matt '
No revision control detected.
Directory initialized.

$ be new 'Write something justifying yet another web framework'
Guessing id 'matt '
Guessing id 'matt '
Created bug with ID 4d4

Not quite the same experience!

Here’s what a ditz issue looks like:

$ ditz show documentation-1
Issue documentation-1
Title: Write something justifying yet another web framework
Description: Why not just polish any of the ones already out there?
Type: task
Status: unstarted
Creator: Matthew Wilson
Age: four minutes
Identifier: e8a4a43f78ee83300cc0372a13375d9534b97abb

Event log:
- created (matt, four minutes ago)

And in be:

$ be show 4d4
Guessing id 'matt '
ID : 4d4e6a17-2097-42bb-a3cd-3c17566ecce8
Short name : 4d4
Severity : minor
Status : open
Assigned :
Target :
Creator : matt
Created : Mon, 22 Dec 2008 20:25 (Tue, 23 Dec 2008 01:25:04 +0000)
Write something justifying yet another web framework

Ditz issues have titles, long descriptions, types (feature, bugfix, or task), releases (optionally) and links to components (also optionally). There are ditz plugins to add support for assigning issues to people.

be has most of the same concepts, just with different names.

data serialization and storage

ditz makes a .ditz directory at the top of a project and be makes a .be directory in the top of the project.

Inside the .ditz folder, there’s one project.yaml file that lists releases (groupings of issues) and components (also groupings of issues, but cross-cutting). Then each issue lives in its own yaml file, and they look like this:

$ cat .ditz/issue-ac3177b3bf8c6757625977ef27279c1fe05df662.yaml
--- !ditz.rubyforge.org,2008-03-06/issue
title: Write some "WHY?" documentation
desc: Justify the existence of this project.
type: :task
component: documentation
reporter: Matthew Wilson
status: :unstarted
creation_time: 2008-12-23 00:59:05.840956 Z
references: []

id: ac3177b3bf8c6757625977ef27279c1fe05df662
- - 2008-12-23 00:59:05.841349 Z
- Matthew Wilson
- created
- ""
- - 2008-12-23 01:08:58.605955 Z
- Matthew Wilson
- commented
- |-
Yeah, if you're gonna build another web framework, this needs to be
really good.

Meanwhile, be is fairly similar, but bugs get whole directories to themselves. be uses what seems to be a home-made plain-text format for storing bugs:

$ cat .be/bugs/4da8ee85-9353-4a92-a654-8510bb8be0d0/values




summary=Write some "WHY?" documentation

time=Tue, 23 Dec 2008 01:12:13 +0000

There’s actually much more whitespace than that. I replaced the eight blank lines between each line of text with just two blank lines.

While ditz stores the comments inside the issue’s yaml file, be makes a directory under the issue’s directory, and then stores the text of the comments in one file and the information about who said it in a separate file.

The community

The ditz mailing list is really active with people debating ideas for new features. The be mailing list is now showing some signs of life after looking dead in August.

What ditz has that be lacks

ditz can make really pretty HTML pages for all the issues for a project. example.

yaml was a really good choice. yaml makes it easy to deserialize to higher objects than just crappy boring primitive types like arrays. Instead, you can hop all the way to your own weird home made objects by specifying a tag. Then all the stuff in the yaml file gets passed in to your object.

Ditz has lots and lots of commands that are only on the be roadmap. You can search your issues with regular expressions with ditz grep, you can claim issues for yourself, you can group issues by releases and components, etc, etc, etc.

The ditz issue data model can be extended with plugins. Like I mentioned earlier, one plugin makes it possible for people to claim issues as assigned to them.

What I like about be

It’s written in python. I hate to feed the python snobbery monster, but there are certain python niceties that I don’t like doing without. In particular, ipython is just too awesome. When I read the ditz code, I spent most of my time navigating the code to get to the part that I cared about that. With ipython, I don’t have that problem. I just hit foo?? and immediately see the source code.

And ruby’s documentation is not what I’ve grown accustomed to with python. For comparison:

I think the Python docs have more explanatory text in just the table of contents.

In addition, ditz uses a lot homemade code: there’s a homemade option parser library (trollop), a homemade hack on the way ruby stores data files so that all the HTML templates are available, and all sorts of gymnastic FP tricks to get a lot of shit done in a very small number of lines. That’s cool, but as a yellow-belt in Ruby, it is really @#$ing hard to make any contributions to this project. Here’s some code that I find a little difficult to read:

def operation method, desc, *args_spec, &options_blk
@operations ||= {}
@operations[method] = { :desc => desc, :args_spec => args_spec,
:options_blk => options_blk }

operation :stop, "Stop work on an issue", :started_issue do
opt :comment, "Specify a comment", :short => 'm', :type => String
opt :no_comment, "Skip asking for a comment", :default => false

def stop project, config, opts, issue
puts "Stopping work on issue #{issue.name}: #{issue.title}."
issue.stop_work config.user, get_comment(opts)
puts "Recorded work stop for #{issue.name}."

After tracing through a few hundred lines of stuff like that, I usually get discouraged and just write a feature request rather than a patch.

In summary

I like ditz. I like reading nearly inscrutable Ruby code to see how wacky people solve problems. My experience with ditz so far has been about an A minus, which is pretty good!

Why I’m going to write my own

There have been a few times where the lack of a proper database system has bit me. Like when I renamed a release, I had to do some searching and replacing in lots and lots of files. Also, regenerating my HTML views is taking almost two minutes now that I have so many issues. Also, certain operations, like moving a handful of issues from one release to another, or searching for intersections of issue subsets, are trickier than what they should be.

Besides all that, I’m fascinated by couchdb, and I think this would be a good use.

I think my system is going to use a local couchdb server that loads in all the issues from local yaml files into the server on startup. Then after lots of work updating, I’ll write out all the issues back into yaml. So, when you update your checkout of your code, you’ll need to restart or reload your couchdb server. Then you can use the couchdb server to work with the system, and then at the end, re-serialize the data back out to JSON, and then to yaml.

ditz and be are sort of like old-school CGI web apps where each user action has to start up some the framework, do the action, then tear down. My system will instead keep all the issue data in memory and require explicit startups and shutdowns.

I wrote some ruby code today

I’ve been using ditz for a few months now. It’s a bugtracking system like trac or bugzilla except that it doesn’t run in a centralized server. Instead, it lives as a bunch of text files inside your SCM.

Ditz is written in ruby, and it uses yaml files to store all the issues (tickets) and groupings of issues. Ditz allows issues to be grouped into releases.

$ ditz releases
0.6 (unreleased)
0.4 (released 2008-07-27)
0.5 (released 2008-08-20)

I wrote an extension that would show all the issues attached to a particular release, like this:

$ ditz help ri
Show issues for a particular release.
Usage: ditz ri

$ ditz ri
Error: command 'ri' requires a release

$ ditz ri bogus
Error: no release with name bogus

$ ditz ri 0.6
x ditz-61: Use text editor for multiline input where possible.
x ditz-72: add model object post-creation validation
x ditz-71: 'ditz add' shouldn't ask for comments
x ditz-76: allow configuration of whether the editor is used or not
x ditz-69: Store issues in .ditz directory by default
_ ditz-42: support tiny issue identifiers (like #34) in the single-component case
x sheila-1: check for a "git push" having updated the issue db, and reload if so
.... lots more issues snipped for brevity.

Thanks to all the really neat plumbing already built into ditz, my patch was trivial to write:

operation :ri, "Show issues for a particular release", :release do
def ri project, config, opts, release
puts todo_list_for(release.issues_from(project))

Ruby is a pretty neat language and people do neat stuff with symbols. In that code above, the operation method takes the symbol :ri and effectively decorates my ri method with the help text. I’m really impressed by how ditz took the fact that my ri method takes a :release symbol as a parameter, and because of that, it knew how to search in the list of releases and then give me the relevant release.

Notes from clerb meeting on Thursday, July 17th

DimpleDough provided a great location for this month’s Cleveland Ruby Users Group and they even shelled out for dinner. We heard a really good talk about about ruby and F#.

The ruby material covered some neat corners of the language like the method_missing method, which operates like python’s getattr. Here’s a toy example of how it can be used:

irb(main):005:0> class C
irb(main):006:1> def foo
irb(main):007:2> 1
irb(main):008:2> end
irb(main):009:1> end
=> nil
irb(main):010:0> c = C.new
=> #
irb(main):011:0> c.foo
=> 1
irb(main):012:0> class C
irb(main):013:1> def method_missing(m, *args)
irb(main):014:2> puts "you tried to call a method #{m}"
irb(main):015:2> end
irb(main):016:1> end
=> nil
irb(main):017:0> c.baz
you tried to call a method baz
=> nil

Incidentally, note how I added a new method to class C after I originally defined it. That’s a cute trick in ruby. I can imagine a lot of nasty misuses of that, but I think the “we’re all consenting adults” rule should apply. And when a class has dozens of methods, it might be helpful to divide them across different files.

We talked about currying as well, in the context of F#. I tend to use currying in this scenario:

  • I recognize that two separate functions could be refactored to be a single function with a whole bunch more parameters;
  • I remake the original functions as curried versions of the new super function.

In other words, if I already have two methods, like paint_it_red(it) and paint_it_green(it), it’s trivial to realize I could write a paint_it_some_color(it, color) and then replace the original paint_it_red with a curried version.

I found this really useful when it isn’t just a single parameter I’m fixing to a constant value, but maybe a whole bunch.

Apparently, Ruby will add currying support in 1.9. I tried to see if I could “fake it” in the irb interpreter, but I just made a mess:

irb(main):036:0> def f(a, b)
irb(main):037:1> a + b
irb(main):038:1> end
=> nil

Nothing interesting so far. f adds its two parameters. So now I’m going to try to make a new function that returns a version of function f with the first parameter a set to fixed value:

irb(main):046:0> def curried_f(a)
irb(main):047:1> def g(b)
irb(main):048:2> a+b
irb(main):049:2> end
irb(main):050:1> return g
irb(main):051:1> end
irb(main):053:0> curried_f(1)
ArgumentError: wrong number of arguments (0 for 1)
from (irb):50:in `g’
from (irb):50:in `curried_f’
from (irb):53
from :0

The problem (I think) stems from how in Ruby, if I just type the name of the function, the function gets called. So in line 50, when I’m trying to return a reference to the new function I just created, Ruby evaluates the result of calling g without giving it any parameters.

I bet I’m doing something very un-ruby-tastic with this approach. I’m probably supposed to leverage those anonymous blocks instead.

F# looks really interesting. It supports all those weird prolog/erlang/haskell-style features like single assignment, pattern matching, and optimal tail-call recursion, with the benefit of having access to the .NET libraries as well.

One of the best professors I studied under made a remark that in COBOL, you think for five minutes and then type for two hours, but in prolog, you think for two hours, and then type for five minutes. I agree. I have learned a lot of languages, but I haven’t gotten any smarter. I’m just learning how to map my thoughts into notation much more quickly.

I would love to have the time and reason to do a project with F#. I think I’ll start by installing mono and messing around.

Notes from Cleveland Ruby meeting on Thursday, Jan 25th

This post contains some python-related information, I promise.

Fun time. Corey Haines explained behavior-driven development and showed some examples using RSpec at last night’s Cleveland Ruby meetup.

As an aside, Corey said “powershell is what the unix command line will be when it grows up” and a thousand angels fell over dead when they heard this blasphemy.

The story-based tests in RSpec seem downright magic. You can write in an english-y syntax:

Given a = 1,
b should return "Hurray"

Or something like that.

I like that RSpec supports a result called “Pending”. This guy writes a good explanation of how it works, and I agree with this remark:

It’s easy enough to rename a test method so it doesn’t execute, but before RSpec I’ve never worked with one where you can mark it as pending and it then reminds you that you still have work to come back too.

I figure that it would be straightforward to add this into nose. Maybe raise a special exception called PendingTest that gets caught differently.

I learned a neat way of using a mock object without having to pass it in as a parameter based on some code I saw last night.

Corey had a couponcontroller that operated on coupon objects. He made a mock coupon object to use with his tests for his couponcontroller. Then, in his test code, he monkeypatched the coupon module so that when somebody said “give me a coupon” he got a mock coupon instead.

I spent a few minutes trying something vaguely like that in python. I’m not sure I like it, but it gets the point across.

I have a file coupon.py:

# This is coupon.py.

class Coupon(object):
"I'm the real coupon"

def foo(self):
print "This is the real coupon"
return "foo"

And I have a file couponcontroller.py:

# This is couponcontroller.py.

from coupon import Coupon

def couponcontroller():
c = Coupon()
return c.foo()

In my test_couponcontroller.py, I want the couponcontroller to use my mock coupon, not the real one.

# This is test_couponcontroller.py.

import couponcontroller

class mockCoupon(object):
"I'm not the real coupon."
def foo(self):
print "Congratulations. You're using a mock."
return "foo"

def setup():
# Mess with the module.
couponcontroller.Coupon = mockCoupon

def test_couponcontroller():
"couponcontroller should return a string 'foo'"
assert couponcontroller.couponcontroller() == "foo"

It seems to work:

$ nosetests -s test_couponcontroller.py
couponcontroller should return a string 'foo' ... Congratulations. You're using a mock.

Ran 1 test in 0.003s


In summary, there’s clearly a lot of smart people in the ruby community, even if they insist on using syntax like

@adder ||= Adder.new