ditz 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ == 0.1 / 2008-04-02
2
+ * Initial release!
@@ -0,0 +1,117 @@
1
+ == ditz
2
+
3
+ by William Morgan <wmorgan-ditz@masanjin.net>
4
+
5
+ http://ditz.rubyforge.org
6
+
7
+ == DESCRIPTION
8
+
9
+ Ditz is a simple, light-weight distributed issue tracker designed to work with
10
+ distributed version control systems like darcs and git. Ditz maintains an issue
11
+ database file on disk, written in a line-based and human-editable format. This
12
+ file can be kept under version control alongside project code, and issue state
13
+ change can then be handled like code changes: modified as part of a commit,
14
+ merged with other state changes from other developers, conflict-resolved in the
15
+ standard manner, etc.
16
+
17
+ Ditz provides a simple, console-based interface for creating and updating the
18
+ issue database file, and some rudimentary HTML generation capabilities for
19
+ producing world-readable status pages. It offers no central public method of
20
+ bug submission.
21
+
22
+ == SYNOPSIS
23
+
24
+ # set up project. creates the bugs.yaml file.
25
+ 1. ditz init
26
+ 2. ditz add-release
27
+
28
+ # add an issue
29
+ 3. ditz add
30
+
31
+ # where am i?
32
+ 4. ditz status
33
+ 5. ditz todo
34
+
35
+ # do work
36
+ 6. write code
37
+ 7. ditz close <issue-id>
38
+ 8. commit
39
+ 9. goto 3
40
+
41
+ # finished!
42
+ 10. ditz release <release-name>
43
+
44
+ == THE DITZ DATA MODEL
45
+
46
+ Ditz includes the bare minimum set of features necessary for open-source
47
+ development. Features like time spent, priority, assignment of tasks to
48
+ developers, due dates, etc. are purposely excluded.
49
+
50
+ A ditz project consists of issues, releases and components.
51
+
52
+ Issues:
53
+ Issues are the fundamental currency of issue tracking. A ditz issue is either
54
+ a feature or a bug, but this distinction doesn't affect anything other than
55
+ how they're displayed.
56
+
57
+ Each issue belongs to exactly one component, and is part of zero or one
58
+ releases.
59
+
60
+ Each issues has an exportable id, in the form of 40 random hex characters.
61
+ This id is "guaranteed" to be unique across all possible issues and
62
+ developers, present and future. Issue ids are typically not exposed to the
63
+ user.
64
+
65
+ Issues also have a non-exportable name, which is short and human-readable.
66
+ All ditz commands use issue names instead of issue ids. Issue ids may change
67
+ in certain circumstances, specifically after a "ditz drop" command.
68
+
69
+ Components:
70
+ There is always one "general" component, named after the project itself. In
71
+ the simplest case, this is the only component, and the user is never bothered
72
+ with the question of which component to assign an issue to.
73
+
74
+ Components simply provide a way of organizing issues, and have no real
75
+ functionality. Issues are assigned names derived form the component they're
76
+ assigned to.
77
+
78
+ Releases:
79
+ A release is the primary grouping mechanism for issues. Status commands like
80
+ "ditz status" and "ditz todo" always group issues by release. When a release
81
+ is 100% complete, it can be marked as released, in which case the associated
82
+ issues will cease appearing in ditz status and todo messages.
83
+
84
+ == FUTURE WORK
85
+
86
+ In future releases, Ditz will have a plugin architecture to allow tighter
87
+ integration with specific SCMs and developer communication channels. (See
88
+ http://ditz.rubyforge.org/ditz/issue-0704dafe4aef96279364013aba177a0971d425cb.html)
89
+
90
+ == LEARNING MORE
91
+
92
+ * ditz help
93
+ * find $DITZ_INSTALL_DIR -type f | xargs cat
94
+
95
+ == REQUIREMENTS
96
+
97
+ * trollop >= 1.7
98
+
99
+ == INSTALLATION
100
+
101
+ Download tarballs from http://rubyforge.org/projects/ditz/, or command your
102
+ computer to "gem install ditz".
103
+
104
+ == LICENSE
105
+
106
+ Copyright (c) 2008 William Morgan.
107
+
108
+ This program is free software: you can redistribute it and/or modify
109
+ it under the terms of the GNU General Public License as published by
110
+ the Free Software Foundation, either version 3 of the License, or
111
+ (at your option) any later version.
112
+
113
+ This program is distributed in the hope that it will be useful,
114
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
115
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
116
+ GNU General Public License for more details.
117
+
@@ -0,0 +1,48 @@
1
+ require 'rubygems'
2
+ require 'hoe'
3
+
4
+ $:.unshift "lib"
5
+ require 'ditz'
6
+
7
+ class Hoe
8
+ def extra_deps; @extra_deps.reject { |x| Array(x).first == "hoe" } end
9
+ end # thanks to "Mike H"
10
+
11
+ Hoe.new('ditz', Ditz::VERSION) do |p|
12
+ p.rubyforge_name = 'ditz'
13
+ p.author = "William Morgan"
14
+ p.summary = "A simple issue tracker designed to integrate well with distributed version control systems like git and darcs. State is saved to a YAML file kept under version control, allowing issues to be closed/added/modified as part of a commit."
15
+
16
+ p.description = p.paragraphs_of('README.txt', 4..5, 9..18).join("\n\n").gsub(/== SYNOPSIS/, "Synopsis")
17
+ p.url = "http://ditz.rubyforge.org"
18
+ p.changes = p.paragraphs_of('Changelog', 0..0).join("\n\n")
19
+ p.email = "wmorgan-ditz@masanjin.net"
20
+ p.extra_deps = [['trollop', '>= 1.7']]
21
+ end
22
+
23
+ WWW_FILES = FileList["www/*"] + %w(README.txt)
24
+ SCREENSHOTS = FileList["www/ss?.png"]
25
+ SCREENSHOTS_SMALL = []
26
+ SCREENSHOTS.each do |fn|
27
+ fn =~ /ss(\d+)\.png/
28
+ sfn = "www/ss#{$1}-small.png"
29
+ file sfn => [fn] do |t|
30
+ sh "cat #{fn} | pngtopnm | pnmscale -xysize 320 240 | pnmtopng > #{sfn}"
31
+ end
32
+ SCREENSHOTS_SMALL << sfn
33
+ end
34
+
35
+ task :upload_webpage => WWW_FILES do |t|
36
+ sh "scp -C #{t.prerequisites * ' '} wmorgan@rubyforge.org:/var/www/gforge-projects/ditz/"
37
+ end
38
+
39
+ task :upload_webpage_images => (SCREENSHOTS + SCREENSHOTS_SMALL) do |t|
40
+ sh "scp -C #{t.prerequisites * ' '} wmorgan@rubyforge.org:/var/www/gforge-projects/ditz/"
41
+ end
42
+
43
+ task :upload_report do |t|
44
+ sh "ditz html ditz"
45
+ sh "scp -Cr ditz wmorgan@rubyforge.org:/var/www/gforge-projects/ditz/"
46
+ end
47
+
48
+ # vim: syntax=ruby
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'trollop'; include Trollop
5
+ require "ditz"
6
+
7
+ $opts = options do
8
+ version "ditz #{Ditz::VERSION} (c) 2008 William Morgan"
9
+ opt :issue_file, "Issue database file", :default => "bugs.yaml"
10
+ opt :config_file, "Configuration file", :default => File.join(ENV["HOME"], ".ditz-config")
11
+ opt :verbose, "Verbose output", :default => false
12
+ end
13
+
14
+ cmd = ARGV.shift or die "expecting a ditz command"
15
+ op = Ditz::Operator.new
16
+
17
+ case cmd # special cases: init and help
18
+ when "init"
19
+ fn = $opts[:issue_file]
20
+ die "#{fn} already exists" if File.exists? fn
21
+ project = op.init
22
+ project.save! fn
23
+ puts "Ok, #{fn} created successfully."
24
+ exit
25
+ when "help"
26
+ op.help
27
+ exit
28
+ end
29
+
30
+ Ditz::debug "loading issues from #{$opts[:issue_file]}"
31
+ project = begin
32
+ Ditz::Project.from $opts[:issue_file]
33
+ rescue SystemCallError, Ditz::Project::Error => e
34
+ die "#{e.message} (use 'init' to initialize)"
35
+ end
36
+
37
+ project.validate!
38
+ project.assign_issue_names!
39
+ project.each_modelobject { |o| o.after_deserialize project }
40
+
41
+ config = begin
42
+ fn = ".ditz-config"
43
+ if File.exists? fn
44
+ Ditz::debug "loading config from #{fn}"
45
+ Ditz::Config.from fn
46
+ else
47
+ Ditz::debug "loading config from #{$opts[:config_file]}"
48
+ Ditz::Config.from $opts[:config_file]
49
+ end
50
+ rescue SystemCallError, Ditz::ModelObject::ModelError => e
51
+ puts <<EOS
52
+ I wasn't able to find a configuration file #{$opts[:config_file]}.
53
+ We'll set it up right now.
54
+ EOS
55
+ Ditz::Config.create_interactively
56
+ end
57
+
58
+ unless op.has_operation? cmd
59
+ die "no such command: #{cmd}"
60
+ end
61
+
62
+ ## talk about the law of unintended consequences. 'gets' requires this.
63
+ args = []
64
+ args << ARGV.shift until ARGV.empty?
65
+
66
+ Ditz::debug "executing command #{cmd}"
67
+ op.do cmd, project, config, *args
68
+
69
+ dirty = project.each_modelobject { |o| break true if o.changed? } || false
70
+ if dirty
71
+ Ditz::debug "project is dirty, saving"
72
+ project.each_modelobject { |o| o.before_serialize project }
73
+ project.save! $opts[:issue_file]
74
+ end
75
+ config.save! $opts[:config_file] if config.changed?
76
+
77
+ # vim: syntax=ruby
@@ -0,0 +1,18 @@
1
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
2
+ <head>
3
+ <title>Component <%= component.name %></title>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf8" />
5
+ <link rel="stylesheet" href="style.css" type="text/css" />
6
+ </head>
7
+
8
+ <body>
9
+
10
+ <%= link_to "index", "&laquo; #{project.name} project page" %>
11
+
12
+ <h1><%= project.name %> component: <%= component.name %></h1>
13
+
14
+ <%= render "issue_table", :show_component => false, :show_release => true %>
15
+
16
+ <p class="footer">Generated <%= Time.now %> by <a href="http://ditz.rubyforge.org/">ditz</a>.
17
+ </body>
18
+ </html>
@@ -0,0 +1,13 @@
1
+ module Ditz
2
+
3
+ VERSION = "0.1"
4
+
5
+ def debug s
6
+ puts "# #{s}" if $opts[:verbose]
7
+ end
8
+ module_function :debug
9
+
10
+ end
11
+
12
+ require 'model-objects'
13
+ require 'operator'
@@ -0,0 +1,41 @@
1
+ require 'erb'
2
+
3
+ module Ditz
4
+
5
+ ## pass through any variables needed for template generation, and add a bunch
6
+ ## of HTML formatting utility methods.
7
+ class ErbHtml
8
+ def initialize template_dir, template_name, links, mapping={}
9
+ @template_name = template_name
10
+ @template_dir = template_dir
11
+ @links = links
12
+ @mapping = mapping
13
+
14
+ @@erbs ||= {}
15
+ @@erbs[template_name] ||= ERB.new(IO.readlines(File.join(template_dir, "#{template_name}.rhtml")).join)
16
+ end
17
+
18
+ def h o; o.to_s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;") end
19
+ def p o; "<p>" + h(o.to_s).gsub("\n\n", "</p><p>") + "</p>" end
20
+ def obscured_email e; h e.gsub(/@.*?(>|$)/, "@...\\1") end
21
+ def link_to o, name
22
+ dest = @links[o]
23
+ dest = o if dest.nil? && o.is_a?(String)
24
+ raise ArgumentError, "no link for #{o.inspect}" unless dest
25
+ "<a href=\"#{dest}\">#{name}</a>"
26
+ end
27
+
28
+ def render template_name, morevars={}
29
+ ErbHtml.new(@template_dir, template_name, @links, @mapping.merge(morevars)).to_s
30
+ end
31
+
32
+ def method_missing meth, *a
33
+ @mapping.member?(meth) ? @mapping[meth] : super
34
+ end
35
+
36
+ def to_s
37
+ @@erbs[@template_name].result binding
38
+ end
39
+ end
40
+
41
+ end
@@ -0,0 +1,83 @@
1
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
2
+ <head>
3
+ <title><%= project.name %> Issue Tracker</title>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf8" />
5
+ <link rel="stylesheet" href="style.css" type="text/css" />
6
+ </head>
7
+ <body>
8
+
9
+ <h1><%= project.name %> Issue Tracker</h1>
10
+
11
+ <h2>Upcoming Releases</h2>
12
+ <% if upcoming_releases.empty? %>
13
+ <p>No upcoming releases.</p>
14
+ <% else %>
15
+ <ul>
16
+ <% upcoming_releases.each do |r| %>
17
+ <%
18
+ issues = project.issues_for_release r
19
+ num_done = issues.count_of { |i| i.closed? }
20
+ pct_done = issues.size == 0 ? 100 : (100.0 * num_done / issues.size)
21
+ num_bugs_todo = issues.count_of { |i| i.bug? && i.open? }
22
+ num_feats_todo = issues.count_of { |i| i.feature? && i.open? }
23
+ %>
24
+ <li>
25
+ <%= link_to r, "Release #{r.name}" %>:
26
+ <% if issues.empty? %>
27
+ no issues
28
+ <% else %>
29
+ <%= sprintf "%.0f%%", pct_done %> complete;
30
+ <%= "bug".pluralize num_bugs_todo %> and
31
+ <%= "feature".pluralize num_feats_todo %> open.
32
+ <% end %>
33
+ </li>
34
+ <% end %>
35
+ </ul>
36
+ <% end %>
37
+
38
+ <h2>Past Releases</h2>
39
+ <% if past_releases.empty? %>
40
+ <p>No past releases.</p>
41
+ <% else %>
42
+ <ul>
43
+ <% past_releases.each do |r| %>
44
+ <li><%= link_to r, "Release #{r.name}" %>, released <%= r.release_time.pretty_date %>. </li>
45
+ <% end %>
46
+ </ul>
47
+ <% end %>
48
+
49
+ <h2>Unassigned issues</h2>
50
+ <%
51
+ issues = project.unassigned_issues
52
+ open_issues = issues.select { |i| i.open? }
53
+ %>
54
+ <p>
55
+ <%= link_to "unassigned", "unassigned issue".pluralize(issues.size).ucfirst %>; <%= open_issues.size.to_pretty_s %> open.
56
+ </p>
57
+
58
+ <% if components.size > 1 %>
59
+ <h2>Open Issues by component</h2>
60
+ <ul>
61
+ <% components.each do |c| %>
62
+ <%
63
+ open_issues = project.issues_for_component(c).select { |i| i.open? }
64
+ num_bugs_todo = open_issues.count_of { |i| i.bug? }
65
+ num_feats_todo = open_issues.count_of { |i| i.feature? }
66
+ %>
67
+ <li>
68
+ <%= link_to c, c.name %>:
69
+ <% if open_issues.empty? %>
70
+ no open issues.
71
+ <% else %>
72
+ <%= "open bug".pluralize num_bugs_todo %> and
73
+ <%= "open feature".pluralize num_feats_todo %>.
74
+ <% end %>
75
+ </li>
76
+ <% end %>
77
+ </ul>
78
+ <% end %>
79
+
80
+ <p class="footer">Generated <%= Time.now %> by <a href="http://ditz.rubyforge.org/">ditz</a>.
81
+
82
+ </body>
83
+ </html>
@@ -0,0 +1,106 @@
1
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
2
+ <head>
3
+ <title><%= issue.title %></title>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf8" />
5
+ <link rel="stylesheet" href="style.css" type="text/css" />
6
+ </head>
7
+
8
+ <body>
9
+
10
+ <%= link_to "index", "&laquo; #{project.name} project page" %>
11
+
12
+ <h1><%= issue.title %></h1>
13
+
14
+ <%=
15
+ project.issues.inject(p(issue.desc)) do |s, i|
16
+ s.gsub(/\{issue #{i.id}\}/, link_to(i, i.title))
17
+ end.gsub(/\{issue \w+\}/, "[unknown issue]")
18
+ %>
19
+
20
+ <table>
21
+ <tr>
22
+ <td class="attrname">Id:</td>
23
+ <td class="attrval"><%= issue.id %></td>
24
+ </tr>
25
+
26
+ <tr>
27
+ <td class="attrname">Type:</td>
28
+ <td class="attrval"><%= issue.type %></td>
29
+ </tr>
30
+
31
+ <tr>
32
+ <td class="attrname">Creation time:</td>
33
+ <td class="attrval"><%= issue.creation_time %></td>
34
+ </tr>
35
+
36
+ <tr>
37
+ <td class="attrname">Creator:</td>
38
+ <td class="attrval"><%=obscured_email issue.reporter %></td>
39
+ </tr>
40
+
41
+ <% unless issue.references.empty? %>
42
+ <tr>
43
+ <td class="attrname">References:</td>
44
+ <td class="attrval">
45
+ <% issue.references.each_with_index do |r, i| %>
46
+ [<%= i + 1 %>] <%= link_to r, r %><br/>
47
+ <% end %>
48
+ </td>
49
+ </tr>
50
+
51
+ <% end %>
52
+
53
+ <tr>
54
+ <td class="attrname">Release:</td>
55
+ <td class="attrval">
56
+ <% if release %>
57
+ <%= link_to release, release.name %>
58
+ <% if release.released? %>
59
+ (released <%= release.release_time.pretty_date %>)
60
+ <% else %>
61
+ (unreleased)
62
+ <% end %>
63
+ <% else %>
64
+ <%= link_to "unassigned", "unassigned" %>
65
+ <% end %>
66
+ </td>
67
+ </tr>
68
+
69
+ <tr>
70
+ <td class="attrname">Component:</td>
71
+ <td class="attrval"><%= link_to component, component.name %></td>
72
+ </tr>
73
+
74
+ <tr>
75
+ <td class="attrname">Status:</td>
76
+ <td class="attrval">
77
+ <%= issue.status_string %><% if issue.closed? %>: <%= issue.disposition_string %><% end %>
78
+ </td>
79
+ </tr>
80
+ </table>
81
+
82
+ <h2>Issue log</h2>
83
+
84
+ <table>
85
+ <% issue.log_events.each_with_index do |(time, who, what, comment), i| %>
86
+ <% if i % 2 == 0 %>
87
+ <tr class="logentryeven">
88
+ <% else %>
89
+ <tr class="logentryodd">
90
+ <% end %>
91
+ <td class="logtime"><%=h time %></td>
92
+ <td class="logwho"><%=obscured_email who %></td>
93
+ <td class="logwhat"><%=h what %></td>
94
+ </tr>
95
+ <tr><td colspan="3" class="logcomment">
96
+ <% if comment.empty? %>
97
+ <% else %>
98
+ <%=p comment %>
99
+ <% end %>
100
+ </td></tr>
101
+ <% end %>
102
+ </table>
103
+
104
+ <p class="footer">Generated <%= Time.now %> by <a href="http://ditz.rubyforge.org/">ditz</a>.
105
+ </body>
106
+ </html>