ditz 0.4 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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>