ditz 0.4 → 0.5

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.
@@ -6,16 +6,16 @@
6
6
 
7
7
  _ditz()
8
8
  {
9
- cur=${COMP_WORDS[COMP_CWORD]}
9
+ local cur=${COMP_WORDS[COMP_CWORD]}
10
10
  if [ $COMP_CWORD -eq 1 ]; then
11
- COMPREPLY=( $( compgen -W "$(ditz --commands)" $cur ) )
11
+ COMPREPLY=( $( compgen -W "$(ditz --commands)" -- $cur ) )
12
12
  elif [ $COMP_CWORD -eq 2 ]; then
13
- cmd=${COMP_WORDS[1]}
14
- COMPREPLY=( $( compgen -W "$(ditz "$cmd" '<options>' 2>/dev/null)" $cur ) )
13
+ local cmd=${COMP_WORDS[1]}
14
+ COMPREPLY=( $( compgen -W "$(ditz "$cmd" '<options>' 2>/dev/null)" -- $cur ) )
15
15
  elif [ $COMP_CWORD -eq 3 ]; then
16
- cmd=${COMP_WORDS[1]}
17
- parm1=${COMP_WORDS[2]}
18
- COMPREPLY=( $( compgen -W "$(ditz "$cmd" "$parm1" '<options>' 2>/dev/null)" $cur ) )
16
+ local cmd=${COMP_WORDS[1]}
17
+ local parm1=${COMP_WORDS[2]}
18
+ COMPREPLY=( $( compgen -W "$(ditz "$cmd" "$parm1" '<options>' 2>/dev/null)" -- $cur ) )
19
19
  fi
20
20
  }
21
21
 
Binary file
@@ -1,18 +1,24 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
3
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
4
+
1
5
  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
2
6
  <head>
3
7
  <title>Component <%= component.name %></title>
4
- <meta http-equiv="Content-Type" content="text/html; charset=utf8" />
8
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5
9
  <link rel="stylesheet" href="style.css" type="text/css" />
6
10
  </head>
7
11
 
8
12
  <body>
9
13
 
10
- <%= link_to "index", "&laquo; #{project.name} project page" %>
14
+ <div class="main">
11
15
 
12
16
  <h1><%= project.name %> component: <%= component.name %></h1>
17
+ <div class="backptr"><%= link_to "index", "&laquo; #{project.name} project page" %></div>
13
18
 
19
+ <h2>All issues</h2>
14
20
  <%= render "issue_table", :show_component => false, :show_release => true %>
15
-
16
- <p class="footer">Generated by <a href="http://ditz.rubyforge.org/">ditz</a>.
21
+ </div>
22
+ <div class="footer">Generated by <a href="http://ditz.rubyforge.org/">ditz</a>.</div>
17
23
  </body>
18
24
  </html>
@@ -1,9 +1,9 @@
1
1
  module Ditz
2
2
 
3
- VERSION = "0.4"
3
+ VERSION = "0.5"
4
4
 
5
5
  def debug s
6
- puts "# #{s}" if $opts[:verbose]
6
+ puts "# #{s}" if $verbose
7
7
  end
8
8
  module_function :debug
9
9
 
@@ -46,11 +46,23 @@ def find_ditz_file fn
46
46
  File.expand_path File.join(dir, fn)
47
47
  end
48
48
 
49
- module_function :home_dir, :find_dir_containing, :find_ditz_file
49
+ def load_plugins fn
50
+ Ditz::debug "loading plugins from #{fn}"
51
+ plugins = YAML::load_file $opts[:plugins_file]
52
+ plugins.each do |p|
53
+ fn = Ditz::find_ditz_file "plugins/#{p}.rb"
54
+ Ditz::debug "loading plugin #{p.inspect} from #{fn}"
55
+ require File.expand_path(fn)
56
+ end
57
+ plugins
58
+ end
59
+
60
+ module_function :home_dir, :find_dir_containing, :find_ditz_file, :load_plugins
50
61
  end
51
62
 
52
63
  require 'model-objects'
53
64
  require 'operator'
54
65
  require 'views'
55
66
  require 'hook'
67
+ require 'file-storage'
56
68
 
@@ -0,0 +1,54 @@
1
+ module Ditz
2
+
3
+ ## stores ditz database on disk
4
+ class FileStorage
5
+ PROJECT_FN = "project.yaml"
6
+ ISSUE_FN_GLOB = "issue-*.yaml"
7
+
8
+ def ISSUE_TO_FN i; "issue-#{i.id}.yaml" end
9
+
10
+ def initialize base_dir
11
+ @base_dir = base_dir
12
+ @project_fn = File.join @base_dir, PROJECT_FN
13
+ end
14
+
15
+ def load
16
+ Ditz::debug "loading project from #{@project_fn}"
17
+ project = Project.from @project_fn
18
+
19
+ fn = File.join @base_dir, ISSUE_FN_GLOB
20
+ Ditz::debug "loading issues from #{fn}"
21
+ project.issues = Dir[fn].map { |fn| Issue.from fn }
22
+ Ditz::debug "found #{project.issues.size} issues"
23
+
24
+ project.issues.each { |i| i.project = project }
25
+ project
26
+ end
27
+
28
+ def save project
29
+ dirty = false
30
+ dirty = project.each_modelobject { |o| break true if o.changed? }
31
+ if dirty
32
+ Ditz::debug "project is dirty, saving #{@project_fn}"
33
+ project.save! @project_fn
34
+ end
35
+
36
+ changed_issues = project.issues.select { |i| i.changed? }
37
+ changed_issues.each do |i|
38
+ fn = filename_for_issue i
39
+ Ditz::debug "issue #{i.name} is dirty, saving #{fn}"
40
+ i.save! fn
41
+ end
42
+
43
+ project.deleted_issues.each do |i|
44
+ fn = filename_for_issue i
45
+ Ditz::debug "issue #{i.name} has been deleted, deleting #{fn}"
46
+ FileUtils.rm fn
47
+ end
48
+ end
49
+
50
+ def filename_for_issue i; File.join @base_dir, ISSUE_TO_FN(i) end
51
+ def filename_for_project; @project_fn end
52
+ end
53
+
54
+ end
Binary file
Binary file
@@ -57,7 +57,7 @@ EOS
57
57
  files = dirs.map { |d| Dir[File.join(d, "*.rb")] }.flatten
58
58
  files.each do |fn|
59
59
  Ditz::debug "loading hook file #{fn}"
60
- load fn
60
+ require File.expand_path(fn)
61
61
  end
62
62
  end
63
63
 
@@ -48,16 +48,51 @@ class ErbHtml
48
48
  raise ArgumentError, "no link for #{o.inspect}" unless dest
49
49
  "<a href=\"#{dest}\">#{name}</a>"
50
50
  end
51
- def fancy_issue_link_for i
52
- "<span class=\"issuestatus_#{i.status}\">" + link_to(i, "[#{i.title}]") + "</span>"
51
+
52
+ def issue_status_img_for i, opts={}
53
+ fn, title = if i.closed?
54
+ case i.disposition
55
+ when :fixed; ["green-check.png", "fixed"]
56
+ when :wontfix; ["red-check.png", "won't fix"]
57
+ when :reorg; ["blue-check.png", "reorganized"]
58
+ end
59
+ elsif i.in_progress?
60
+ ["green-bar.png", "in progress"]
61
+ elsif i.paused?
62
+ ["yellow-bar.png", "paused"]
63
+ end
64
+
65
+ return "" unless fn
66
+
67
+ args = {:src => fn, :alt => title, :title => title}
68
+ args[:class] = opts[:class] if opts[:class]
69
+
70
+ "<img " + args.map { |k, v| "#{k}=#{v.inspect}" }.join(" ") + "/>"
53
71
  end
54
72
 
55
- def link_issue_names project, s
73
+ def issue_link_for i, opts={}
74
+ link = link_to i, "#{i.title}"
75
+ link = "<span class=\"inline-issue-link\">" + link + "</span>" if opts[:inline]
76
+ link = link + " " + issue_status_img_for(i, :class => "inline-status-image") if opts[:status_image]
77
+ link
78
+ end
79
+
80
+ def link_issue_names project, s, opts={}
56
81
  project.issues.inject(s) do |s, i|
57
- s.gsub(/\b#{i.name}\b/, fancy_issue_link_for(i))
82
+ s.gsub(/\b#{i.name}\b/, issue_link_for(i, {:inline => true, :status_image => true}.merge(opts)))
58
83
  end
59
84
  end
60
85
 
86
+ def progress_meter p, size=50
87
+ done = (p * size).to_i
88
+ undone = [size - done, 0].max
89
+ "<span class='progress-meter'><span class='progress-meter-done'>" +
90
+ ("&nbsp;" * done) +
91
+ "</span><span class='progress-meter-undone'>" +
92
+ ("&nbsp;" * undone) +
93
+ "</span></span>"
94
+ end
95
+
61
96
  ## render a nested ERB
62
97
  alias :render :render_template
63
98
 
@@ -1,51 +1,65 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
3
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
4
+
1
5
  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
2
6
  <head>
3
7
  <title><%= project.name %> Issue Tracker</title>
4
- <meta http-equiv="Content-Type" content="text/html; charset=utf8" />
8
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5
9
  <link rel="stylesheet" href="style.css" type="text/css" />
6
10
  </head>
7
11
  <body>
8
12
 
13
+ <div class="main">
9
14
  <h1><%= project.name %> Issue Tracker</h1>
10
15
 
11
16
  <h2>Upcoming Releases</h2>
12
17
  <% if upcoming_releases.empty? %>
13
18
  <p>No upcoming releases.</p>
14
19
  <% 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
- open_issues = project.group_issues(issues.select { |i| i.open? })
22
- %>
23
- <li>
24
- <%= link_to r, "Release #{r.name}" %>:
25
- <% if issues.empty? %>
26
- no issues
27
- <% else %>
28
- <%= sprintf "%.0f%%", pct_done %> complete;
29
- <% if open_issues.empty? %>
30
- ready for release!
20
+ <table>
21
+ <tbody>
22
+ <% upcoming_releases.each do |r| %>
23
+ <%
24
+ issues = project.issues_for_release r
25
+ num_done = issues.count_of { |i| i.closed? }
26
+ pct_done = issues.size == 0 ? 1.0 : (num_done.to_f / issues.size.to_f)
27
+ open_issues = issues.select { |i| i.open? }
28
+ %>
29
+ <tr><td>
30
+ <%= link_to r, "#{r.name}" %>
31
+ </td>
32
+ <td>
33
+ <% if issues.empty? %>
34
+ no issues
35
+ <% elsif open_issues.empty? %>
36
+ ready for release!
31
37
  <% else %>
32
- <%= open_issues.map { |n,is| n.to_s.pluralize is.size }.join(' and ') %> open.
38
+ <%= progress_meter pct_done %>
39
+ <%= sprintf "%.0f%%", pct_done * 100.0 %> complete
40
+ </td>
41
+ </tr><tr><td></td><td>
42
+ <%= num_done %> / <%= issues.size %> issues.
43
+ <%= link_to r, "See issues &raquo;" %>
33
44
  <% end %>
34
- <% end %>
35
- </li>
36
- <% end %>
37
- </ul>
45
+ </td>
46
+ </tr>
47
+ <% end %>
48
+ </tbody>
49
+ </table>
38
50
  <% end %>
39
51
 
40
52
  <h2>Past Releases</h2>
41
53
  <% if past_releases.empty? %>
42
54
  <p>No past releases.</p>
43
55
  <% else %>
44
- <ul>
45
- <% past_releases.sort_by { |r| r.release_time }.reverse.each do |r| %>
46
- <li><%= link_to r, "Release #{r.name}" %>, released <%= r.release_time.pretty_date %>. </li>
47
- <% end %>
48
- </ul>
56
+ <table>
57
+ <tbody>
58
+ <% past_releases.sort_by { |r| r.release_time }.reverse.each do |r| %>
59
+ <tr><td><%= link_to r, r.name %></td><td class="littledate">on <%= r.release_time.pretty_date %></td></tr>
60
+ <% end %>
61
+ </tbody>
62
+ </table>
49
63
  <% end %>
50
64
 
51
65
  <h2>Unassigned issues</h2>
@@ -57,53 +71,60 @@
57
71
  <% if issues.empty? %>
58
72
  No unassigned issues.
59
73
  <% else %>
60
- <%= link_to "unassigned", "unassigned issue".pluralize(issues.size).capitalize %>; <%= open_issues.size.to_pretty_s %> of them open.
74
+ <%= link_to "unassigned", "unassigned issue".pluralize(issues.size).capitalize %> (<%= open_issues.size.to_pretty_s %> open).
61
75
  <% end %>
62
76
  </p>
63
77
 
64
78
  <% if components.size > 1 %>
65
79
  <h2>Open Issues by component</h2>
66
- <ul>
67
- <% components.each do |c| %>
68
- <%
69
- open_issues = project.group_issues(project.issues_for_component(c).select { |i| i.open? })
70
- %>
71
- <li>
80
+ <table>
81
+ <tbody>
82
+ <% components.each do |c| %>
83
+ <%
84
+ issues = project.issues_for_component c
85
+ num_done = issues.count_of { |i| i.closed? }
86
+ pct_done = issues.size == 0 ? 1.0 : (num_done.to_f / issues.size.to_f)
87
+ open_issues = issues.select { |i| i.open? }
88
+ %>
72
89
  <% if open_issues.empty? %>
73
- <span class="dimmed">
74
- <%= link_to c, c.name %>:
75
- no open issues.
76
- </span>
90
+ <tr class="dimmed">
77
91
  <% else %>
78
- <%= link_to c, c.name %>:
79
- <%= open_issues.map { |n,is| n.to_s.pluralize is.size }.join(' and ') %> open.
92
+ <tr>
80
93
  <% end %>
81
- </li>
82
- <% end %>
83
- </ul>
94
+ <td>
95
+ <%= link_to c, c.name %>
96
+ </td><td>
97
+ <%= "open issue".pluralize(open_issues.size) %>
98
+ </td></tr>
99
+ <% end %>
100
+ </tbody>
101
+ </table>
84
102
  <% end %>
85
103
 
86
104
  <h2>Recent activity</h2>
87
105
 
88
- <table>
89
- <% project.issues.map { |i| i.log_events.map { |e| [e, i] } }.
90
- flatten_one_level.
91
- sort_by { |e| e.first.first }.
92
- reverse[0 ... 10].
93
- each do |(date, who, what, comment), i| %>
94
- <tr>
95
- <td><%= date.pretty_date %></td>
96
- <td class="issuename">
97
- <%= link_issue_names project, h(i.name) %>
98
- <%= what %> by <%= who.shortened_email %></td>
99
- </tr>
100
- <% if comment && comment =~ /\S/ %>
101
- <tr><td></td><td><i><%= link_issue_names project, h(comment) %></i></td></tr>
106
+ <table class="log">
107
+ <tbody>
108
+ <% project.issues.map { |i| i.log_events.map { |e| [e, i] } }.
109
+ flatten_one_level.
110
+ sort_by { |e| e.first.first }.
111
+ reverse[0 ... 10].
112
+ each_with_index do |((date, who, what, comment), i), idx| %>
113
+ <tr class="<%= idx % 2 == 0 ? "even-row" : "odd-row" %>">
114
+ <td class="date"><%= date.pretty_date %></td>
115
+ <td class="issuename">
116
+ <%= issue_link_for i, :status_image => true %>
117
+ </td>
118
+ <td> <%= what %> </td>
119
+ </tr>
120
+ <tr><td></td></tr>
102
121
  <% end %>
103
- <% end %>
122
+ </tbody>
104
123
  </table>
105
124
 
106
- <p class="footer">Generated by <a href="http://ditz.rubyforge.org/">ditz</a>.
125
+ </div>
126
+
127
+ <div class="footer">Generated by <a href="http://ditz.rubyforge.org/">ditz</a>.</div>
107
128
 
108
129
  </body>
109
130
  </html>
@@ -1,107 +1,119 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
3
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
4
+
1
5
  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
2
6
  <head>
3
7
  <title><%= issue.title %></title>
4
- <meta http-equiv="Content-Type" content="text/html; charset=utf8" />
8
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5
9
  <link rel="stylesheet" href="style.css" type="text/css" />
6
10
  </head>
7
11
 
8
12
  <body>
9
13
 
10
- <%= link_to "index", "&laquo; #{project.name} project page" %>
14
+ <div class="main">
11
15
 
12
16
  <h1><%= link_issue_names project, issue.title %></h1>
17
+ <div class="backptr"><%= link_to "index", "&laquo; #{project.name} project page" %></div>
13
18
 
14
- <%= link_issue_names project, p(issue.desc) %>
19
+ <% if issue.desc && !issue.desc.empty? %>
20
+ <div class="description">
21
+ <%= link_issue_names project, p(issue.desc) %>
22
+ </div>
23
+ <% end %>
15
24
 
25
+ <h2>Details</h2>
16
26
  <table>
17
- <tr>
18
- <td class="attrname">Id:</td>
19
- <td class="attrval"><%= issue.id %></td>
20
- </tr>
21
-
22
- <tr>
23
- <td class="attrname">Type:</td>
24
- <td class="attrval"><%= issue.type %></td>
25
- </tr>
26
-
27
- <tr>
28
- <td class="attrname">Creation time:</td>
29
- <td class="attrval"><%= issue.creation_time %></td>
30
- </tr>
31
-
32
- <tr>
33
- <td class="attrname">Creator:</td>
34
- <td class="attrval"><%=obscured_email issue.reporter %></td>
35
- </tr>
36
-
37
- <% unless issue.references.empty? %>
38
- <tr>
39
- <td class="attrname">References:</td>
40
- <td class="attrval">
41
- <% issue.references.each_with_index do |r, i| %>
42
- [<%= i + 1 %>] <%= link_to r, r %><br/>
43
- <% end %>
44
- </td>
45
- </tr>
27
+ <tbody>
28
+ <tr>
29
+ <td class="attrname">Id:</td>
30
+ <td class="attrval"><span class="id"><%= issue.id %></span></td>
31
+ </tr>
32
+
33
+ <tr>
34
+ <td class="attrname">Type:</td>
35
+ <td class="attrval"><%= issue.type %></td>
36
+ </tr>
37
+
38
+ <tr>
39
+ <td class="attrname">Creation time:</td>
40
+ <td class="attrval"><%=t issue.creation_time %></td>
41
+ </tr>
42
+
43
+ <tr>
44
+ <td class="attrname">Creator:</td>
45
+ <td class="attrval"><span class="person"><%=obscured_email issue.reporter %></span></td>
46
+ </tr>
47
+
48
+ <% unless issue.references.empty? %>
49
+ <tr>
50
+ <td class="attrname">References:</td>
51
+ <td class="attrval">
52
+ <% issue.references.each_with_index do |r, i| %>
53
+ [<%= i + 1 %>] <%= link_to r, r %><br/>
54
+ <% end %>
55
+ </td>
56
+ </tr>
46
57
 
47
- <% end %>
58
+ <% end %>
48
59
 
49
- <tr>
50
- <td class="attrname">Release:</td>
51
- <td class="attrval">
52
- <% if release %>
53
- <%= link_to release, release.name %>
54
- <% if release.released? %>
55
- (released <%= release.release_time.pretty_date %>)
60
+ <tr>
61
+ <td class="attrname">Release:</td>
62
+ <td class="attrval">
63
+ <% if release %>
64
+ <%= link_to release, release.name %>
65
+ <% if release.released? %>
66
+ (released <%= release.release_time.pretty_date %>)
67
+ <% else %>
68
+ (unreleased)
69
+ <% end %>
56
70
  <% else %>
57
- (unreleased)
71
+ <%= link_to "unassigned", "unassigned" %>
58
72
  <% end %>
59
- <% else %>
60
- <%= link_to "unassigned", "unassigned" %>
61
- <% end %>
62
- </td>
63
- </tr>
64
-
65
- <tr>
66
- <td class="attrname">Component:</td>
67
- <td class="attrval"><%= link_to component, component.name %></td>
68
- </tr>
69
-
70
- <tr>
71
- <td class="attrname">Status:</td>
72
- <td class="attrval">
73
- <%= issue.status_string %><% if issue.closed? %>: <%= issue.disposition_string %><% end %>
74
- </td>
75
- </tr>
76
-
77
- <%= extra_summary_html %>
78
-
73
+ </td>
74
+ </tr>
75
+
76
+ <tr>
77
+ <td class="attrname">Component:</td>
78
+ <td class="attrval"><%= link_to component, component.name %></td>
79
+ </tr>
80
+
81
+ <tr>
82
+ <td class="attrname">Status:</td>
83
+ <td class="attrval">
84
+ <%= issue.status_string %><% if issue.closed? %>: <%= issue.disposition_string %><% end %>
85
+ <%= issue_status_img_for issue, :class => "inline-status-image" %>
86
+ </td>
87
+ </tr>
88
+
89
+ <%= extra_summary_html %>
90
+ </tbody>
79
91
  </table>
80
92
 
81
93
  <%= extra_details_html %>
82
94
 
83
95
  <h2>Issue log</h2>
84
96
 
85
- <table>
86
- <% issue.log_events.each_with_index do |(time, who, what, comment), i| %>
87
- <% if i % 2 == 0 %>
88
- <tr class="logentryeven">
89
- <% else %>
90
- <tr class="logentryodd">
91
- <% end %>
92
- <td class="logtime"><%=t time %></td>
93
- <td class="logwho"><%=obscured_email who %></td>
94
- <td class="logwhat"><%=h what %></td>
95
- </tr>
96
- <tr><td colspan="3" class="logcomment">
97
- <% if comment.empty? %>
98
- <% else %>
99
- <%= link_issue_names project, p(comment) %>
97
+ <table class="log">
98
+ <tbody>
99
+ <% issue.log_events.reverse.each_with_index do |(time, who, what, comment), i| %>
100
+ <tr class="<%= i % 2 == 0 ? "even-row" : "odd-row" %>">
101
+ <td class="date"><%=t time %></td>
102
+ <td class="person"><%=obscured_email who %></td>
103
+ <td class="message"><%=h what %></td>
104
+ </tr>
105
+ <% unless comment.empty? %>
106
+ <tr><td colspan="3" class="logcomment">
107
+ <%= link_issue_names project, comment %>
108
+ </td></tr>
100
109
  <% end %>
101
- </td></tr>
102
- <% end %>
110
+ <tr><td></td></tr>
111
+ <% end %>
112
+ </tbody>
103
113
  </table>
104
114
 
105
- <p class="footer">Generated by <a href="http://ditz.rubyforge.org/">ditz</a>.
115
+ </div>
116
+ <div class="footer">Generated by <a href="http://ditz.rubyforge.org/">ditz</a>.</div>
117
+
106
118
  </body>
107
119
  </html>