ohac-ditz 0.5.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.
- data/Changelog +76 -0
- data/INSTALL +20 -0
- data/LICENSE +674 -0
- data/Manifest.txt +48 -0
- data/PLUGINS.txt +197 -0
- data/README.txt +146 -0
- data/Rakefile +66 -0
- data/ReleaseNotes +56 -0
- data/bin/ditz +230 -0
- data/contrib/completion/_ditz.zsh +29 -0
- data/contrib/completion/ditz.bash +38 -0
- data/lib/ditz/file-storage.rb +53 -0
- data/lib/ditz/hook.rb +67 -0
- data/lib/ditz/html.rb +107 -0
- data/lib/ditz/lowline.rb +244 -0
- data/lib/ditz/model-objects.rb +379 -0
- data/lib/ditz/model.rb +339 -0
- data/lib/ditz/operator.rb +655 -0
- data/lib/ditz/plugins/git-sync.rb +83 -0
- data/lib/ditz/plugins/git.rb +153 -0
- data/lib/ditz/plugins/issue-claiming.rb +193 -0
- data/lib/ditz/plugins/issue-labeling.rb +170 -0
- data/lib/ditz/util.rb +61 -0
- data/lib/ditz/view.rb +16 -0
- data/lib/ditz/views.rb +191 -0
- data/lib/ditz.rb +110 -0
- data/man/man1/ditz.1 +38 -0
- data/setup.rb +1585 -0
- data/share/ditz/blue-check.png +0 -0
- data/share/ditz/component.rhtml +24 -0
- data/share/ditz/green-bar.png +0 -0
- data/share/ditz/green-check.png +0 -0
- data/share/ditz/index.rhtml +130 -0
- data/share/ditz/issue.rhtml +119 -0
- data/share/ditz/issue_table.rhtml +28 -0
- data/share/ditz/red-check.png +0 -0
- data/share/ditz/release.rhtml +98 -0
- data/share/ditz/style.css +226 -0
- data/share/ditz/unassigned.rhtml +23 -0
- data/share/ditz/yellow-bar.png +0 -0
- metadata +116 -0
data/bin/ditz
ADDED
@@ -0,0 +1,230 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.expand_path("#{File.dirname($0)}/../lib"))
|
4
|
+
|
5
|
+
## requires are split in two for efficiency reasons: ditz should be really
|
6
|
+
## fast when using it for completion.
|
7
|
+
$KCODE = "u"
|
8
|
+
|
9
|
+
require 'ditz/operator'
|
10
|
+
op = Ditz::Operator.new
|
11
|
+
|
12
|
+
## a secret option for shell completion
|
13
|
+
if ARGV.include? '--commands'
|
14
|
+
puts op.class.operations.map { |name, _| name }
|
15
|
+
exit
|
16
|
+
end
|
17
|
+
|
18
|
+
begin
|
19
|
+
require 'rubygems'
|
20
|
+
# list version dependant gems here.
|
21
|
+
gem 'yaml_waml', '>= 0.3'
|
22
|
+
gem 'trollop', '>= 1.9'
|
23
|
+
rescue LoadError
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'fileutils'
|
27
|
+
require 'pathname'
|
28
|
+
require 'trollop'; include Trollop
|
29
|
+
require "ditz"
|
30
|
+
|
31
|
+
CONFIG_FN = ".ditz-config"
|
32
|
+
PLUGIN_FN = ".ditz-plugins"
|
33
|
+
|
34
|
+
config_dir = Ditz::find_dir_containing CONFIG_FN
|
35
|
+
plugin_dir = Ditz::find_dir_containing PLUGIN_FN
|
36
|
+
|
37
|
+
$opts = options do
|
38
|
+
version "ditz #{Ditz::VERSION}"
|
39
|
+
banner <<EOS
|
40
|
+
Usage: ditz [global-opts] [command] [command-opts]
|
41
|
+
|
42
|
+
See 'ditz help' for a list of commands.
|
43
|
+
|
44
|
+
Global options are:
|
45
|
+
EOS
|
46
|
+
|
47
|
+
opt :issue_dir, "Issue database dir", :type => :string
|
48
|
+
opt :config_file, "Configuration file", :default => File.join(config_dir || ".", CONFIG_FN)
|
49
|
+
opt :plugins_file, "Plugins file", :default => File.join(plugin_dir || ".", PLUGIN_FN)
|
50
|
+
opt :verbose, "Verbose output", :default => false
|
51
|
+
opt :list_hooks, "Print all hooks exit", :short => 'l', :default => false
|
52
|
+
opt :version, "Print version and exit", :short => :none
|
53
|
+
stop_on_unknown
|
54
|
+
end
|
55
|
+
$verbose = true if $opts[:verbose]
|
56
|
+
|
57
|
+
Ditz::HookManager.register :startup, <<EOS
|
58
|
+
Executes at startup
|
59
|
+
|
60
|
+
Variables: project, config
|
61
|
+
No return value.
|
62
|
+
EOS
|
63
|
+
|
64
|
+
Ditz::HookManager.register :after_add, <<EOS
|
65
|
+
Executes before terminating if new issue files has been created.
|
66
|
+
Basically you want to instruct your SCM that these files has
|
67
|
+
been added.
|
68
|
+
|
69
|
+
Variables: project, config, issues
|
70
|
+
No return value.
|
71
|
+
EOS
|
72
|
+
|
73
|
+
Ditz::HookManager.register :after_delete, <<EOS
|
74
|
+
Executes before terminating if new issue files has been deleted.
|
75
|
+
Basically you want to instruct your SCM that these files has
|
76
|
+
been deleted.
|
77
|
+
|
78
|
+
Variables: project, config, issues
|
79
|
+
No return value.
|
80
|
+
EOS
|
81
|
+
|
82
|
+
Ditz::HookManager.register :after_update, <<EOS
|
83
|
+
Executes before terminating if new issue files has been updated.
|
84
|
+
You may want to instruct your SCM about these changes.
|
85
|
+
Note that new issues are not considered updated.
|
86
|
+
|
87
|
+
Variables: project, config, issues
|
88
|
+
No return value.
|
89
|
+
EOS
|
90
|
+
|
91
|
+
if $opts[:list_hooks]
|
92
|
+
Ditz::HookManager.print_hooks
|
93
|
+
exit 0
|
94
|
+
end
|
95
|
+
|
96
|
+
begin
|
97
|
+
Ditz::load_plugins $opts[:plugins_file] if File.exist? $opts[:plugins_file]
|
98
|
+
rescue LoadError => e
|
99
|
+
Ditz::debug "can't load plugins file: #{e.message}"
|
100
|
+
end
|
101
|
+
|
102
|
+
## prevent ctrl-c and borken pipes from printing a useless backtrace
|
103
|
+
def die_gently
|
104
|
+
begin
|
105
|
+
yield
|
106
|
+
rescue Interrupt, Errno::EPIPE
|
107
|
+
puts
|
108
|
+
exit 1
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
config = begin
|
113
|
+
Ditz::debug "loading config from #{$opts[:config_file]}"
|
114
|
+
Ditz::Config.from $opts[:config_file]
|
115
|
+
rescue SystemCallError => e
|
116
|
+
if ARGV.member? "<options>"
|
117
|
+
## special case here. if we're asking for tab completion, and the config
|
118
|
+
## file doesn't exist, don't do the interactive building. just make a
|
119
|
+
## fake empty one and carry on.
|
120
|
+
Ditz::Config.new
|
121
|
+
else
|
122
|
+
puts <<EOS
|
123
|
+
I wasn't able to find a configuration file #{$opts[:config_file]}.
|
124
|
+
We'll set it up right now.
|
125
|
+
EOS
|
126
|
+
die_gently { Ditz::Config.create_interactively.save! $opts[:config_file] }
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
## configure any lowline settings
|
131
|
+
Lowline.use_editor_if_possible = config.use_editor_if_possible
|
132
|
+
|
133
|
+
issue_dir = Pathname.new($opts[:issue_dir] || config.issue_dir)
|
134
|
+
cmd = ARGV.shift || "todo"
|
135
|
+
unless op.has_operation? cmd
|
136
|
+
die "no such command: #{cmd}"
|
137
|
+
end
|
138
|
+
|
139
|
+
## TODO: refactor so that three 'exit' statements aren't required
|
140
|
+
case cmd # some special commands not handled by Ditz::Operator
|
141
|
+
when "init"
|
142
|
+
die "#{issue_dir} directory already exists" if issue_dir.exist?
|
143
|
+
project = nil
|
144
|
+
die_gently { project = op.init }
|
145
|
+
issue_dir.mkdir
|
146
|
+
fn = issue_dir + Ditz::FileStorage::PROJECT_FN
|
147
|
+
project.save! fn
|
148
|
+
puts "Ok, #{issue_dir} directory created successfully."
|
149
|
+
exit
|
150
|
+
when "reconfigure" # might not be able to load the project
|
151
|
+
die_gently { op.do cmd, nil, config, ARGV }
|
152
|
+
exit
|
153
|
+
when "help"
|
154
|
+
begin
|
155
|
+
op.do cmd, nil, config, ARGV
|
156
|
+
rescue Ditz::Operator::Error => e
|
157
|
+
die "#{e.message}"
|
158
|
+
end
|
159
|
+
exit
|
160
|
+
end
|
161
|
+
|
162
|
+
$project_root = Ditz::find_dir_containing(issue_dir + Ditz::FileStorage::PROJECT_FN)
|
163
|
+
die "No #{issue_dir} directory---use 'ditz init' to initialize" unless $project_root
|
164
|
+
$project_root += issue_dir
|
165
|
+
|
166
|
+
storage = Ditz::FileStorage.new $project_root
|
167
|
+
project = begin
|
168
|
+
storage.load
|
169
|
+
rescue SystemCallError, Ditz::Project::Error => e
|
170
|
+
die "#{e.message} (use 'init' to initialize)"
|
171
|
+
end
|
172
|
+
|
173
|
+
Ditz::HookManager.run :startup, project, config
|
174
|
+
|
175
|
+
Ditz::debug "executing command #{cmd}"
|
176
|
+
die_gently do
|
177
|
+
begin
|
178
|
+
op.do cmd, project, config, ARGV
|
179
|
+
## TODO: make these errors have a common ancestor so that this rescue
|
180
|
+
## statement isn't so stupid
|
181
|
+
rescue Ditz::Operator::Error, Ditz::Release::Error, Ditz::Project::Error, Ditz::Issue::Error => e
|
182
|
+
## don't use 'die' here (which is Trollop::die) because this is not a
|
183
|
+
## problem with the command-line arguments.
|
184
|
+
$stderr.puts "Error: #{e.message}"
|
185
|
+
exit 1
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
changed_issues = project.issues.select { |i| i.changed? }
|
190
|
+
changed_not_added_issues = changed_issues - project.added_issues
|
191
|
+
|
192
|
+
storage.save project
|
193
|
+
|
194
|
+
## at this point, for compatibility with older hook stuff, we set the pathname
|
195
|
+
## directly on the issues.
|
196
|
+
|
197
|
+
project.issues.each { |i| i.pathname = storage.filename_for_issue(i) }
|
198
|
+
unless project.added_issues.empty?
|
199
|
+
unless Ditz::HookManager.run :after_add, project, config, project.added_issues
|
200
|
+
puts "You may have to inform your SCM that the following files have been added:"
|
201
|
+
project.added_issues.each { |i| puts " " + storage.filename_for_issue(i) }
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
unless project.deleted_issues.empty?
|
206
|
+
unless Ditz::HookManager.run :after_delete, project, config, project.deleted_issues
|
207
|
+
puts "You may have to inform your SCM that the following files have been deleted:"
|
208
|
+
project.deleted_issues.each { |i| puts " " + storage.filename_for_issue(i) }
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
unless changed_not_added_issues.empty?
|
213
|
+
unless Ditz::HookManager.run :after_update, project, config, changed_not_added_issues
|
214
|
+
puts "You may have to inform your SCM that the following files have been modified:"
|
215
|
+
changed_not_added_issues.each { |i| puts " " + storage.filename_for_issue(i) }
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
## hack upon a hack
|
220
|
+
if project.changed?
|
221
|
+
project.pathname = storage.filename_for_project
|
222
|
+
unless Ditz::HookManager.run :after_update, project, config, [project]
|
223
|
+
puts "You may have to inform your SCM that the following files have been modified:"
|
224
|
+
puts " " + storage.filename_for_project
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
config.save! $opts[:config_file] if config.changed?
|
229
|
+
|
230
|
+
# vim: syntax=ruby
|
@@ -0,0 +1,29 @@
|
|
1
|
+
#compdef ditz
|
2
|
+
|
3
|
+
ME=ditz
|
4
|
+
COMMANDS=--commands
|
5
|
+
OPTIONS='<options>'
|
6
|
+
|
7
|
+
if (($CURRENT == 2)); then
|
8
|
+
# We're completing the first word after the tool: the command.
|
9
|
+
_wanted command expl "$ME command" \
|
10
|
+
compadd -- $( "$ME" "$COMMANDS" )
|
11
|
+
else
|
12
|
+
# Find the options/files/URL/etc. for the current command by using the tool itself.
|
13
|
+
case "${words[$CURRENT]}"; in
|
14
|
+
-*)
|
15
|
+
_wanted args expl "Arguments for $ME ${words[2]}" \
|
16
|
+
compadd -- $( "$ME" "${words[2]}" "$OPTIONS" ; _files )
|
17
|
+
;;
|
18
|
+
ht*|ft*)
|
19
|
+
_arguments '*:URL:_urls'
|
20
|
+
;;
|
21
|
+
/*|./*|\~*|../*)
|
22
|
+
_arguments '*:file:_files'
|
23
|
+
;;
|
24
|
+
*)
|
25
|
+
_wanted args expl "Arguments for $ME ${words[2]}" \
|
26
|
+
compadd -- $( "$ME" "${words[2]}" "$OPTIONS" )
|
27
|
+
;;
|
28
|
+
esac
|
29
|
+
fi
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# ditz bash completion
|
2
|
+
#
|
3
|
+
# author: Christian Garbs
|
4
|
+
#
|
5
|
+
# based on bzr.simple by Martin Pool
|
6
|
+
|
7
|
+
_ditz()
|
8
|
+
{
|
9
|
+
local cur=${COMP_WORDS[COMP_CWORD]}
|
10
|
+
|
11
|
+
if [ $COMP_CWORD -eq 1 ]; then
|
12
|
+
# no command yet, show all commands
|
13
|
+
COMPREPLY=( $( compgen -W "$(ditz --commands)" -- $cur ) )
|
14
|
+
|
15
|
+
else
|
16
|
+
unset COMP_WORDS[COMP_CWORD] # remove last
|
17
|
+
unset COMP_WORDS[0] # remove first
|
18
|
+
|
19
|
+
# add options if applicable...
|
20
|
+
local options
|
21
|
+
if [ "${cur:0:1}" = '-' ]; then
|
22
|
+
# ...but only if at least a dash is given
|
23
|
+
case "${COMP_WORDS[1]}" in
|
24
|
+
add|add_reference|add_release|assign|close|comment|release|set_component|start|stop|unassign)
|
25
|
+
options="--comment --no-comment"
|
26
|
+
;;
|
27
|
+
edit)
|
28
|
+
options="--comment --no-comment --silent"
|
29
|
+
;;
|
30
|
+
esac
|
31
|
+
fi
|
32
|
+
|
33
|
+
# let ditz parse the commandline and print available completions, then append the options form above
|
34
|
+
COMPREPLY=( $( compgen -W "$(ditz "${COMP_WORDS[@]}" '<options>' 2>/dev/null) $options" -- $cur ) )
|
35
|
+
fi
|
36
|
+
}
|
37
|
+
|
38
|
+
complete -F _ditz -o default ditz
|
@@ -0,0 +1,53 @@
|
|
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 = project.each_modelobject { |o| break true if o.changed? }
|
30
|
+
if dirty
|
31
|
+
Ditz::debug "project is dirty, saving #{@project_fn}"
|
32
|
+
project.save! @project_fn
|
33
|
+
end
|
34
|
+
|
35
|
+
changed_issues = project.issues.select { |i| i.changed? }
|
36
|
+
changed_issues.each do |i|
|
37
|
+
fn = filename_for_issue i
|
38
|
+
Ditz::debug "issue #{i.name} is dirty, saving #{fn}"
|
39
|
+
i.save! fn
|
40
|
+
end
|
41
|
+
|
42
|
+
project.deleted_issues.each do |i|
|
43
|
+
fn = filename_for_issue i
|
44
|
+
Ditz::debug "issue #{i.name} has been deleted, deleting #{fn}"
|
45
|
+
FileUtils.rm fn
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def filename_for_issue i; File.join @base_dir, ISSUE_TO_FN(i) end
|
50
|
+
def filename_for_project; @project_fn end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
data/lib/ditz/hook.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
module Ditz
|
2
|
+
class HookManager
|
3
|
+
def initialize
|
4
|
+
@descs = {}
|
5
|
+
@blocks = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
@@instance = nil
|
9
|
+
def self.method_missing m, *a, &b
|
10
|
+
@@instance ||= self.new
|
11
|
+
@@instance.send m, *a, &b
|
12
|
+
end
|
13
|
+
|
14
|
+
def register name, desc
|
15
|
+
@descs[name] = desc
|
16
|
+
@blocks[name] = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def on *names, &block
|
20
|
+
names.each do |name|
|
21
|
+
raise "unregistered hook #{name.inspect}" unless @descs[name]
|
22
|
+
@blocks[name] << block
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def run name, *args
|
27
|
+
raise "unregistered hook #{name.inspect}" unless @descs[name]
|
28
|
+
blocks = hooks_for name
|
29
|
+
return false if blocks.empty?
|
30
|
+
blocks.each { |block| block[*args] }
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def print_hooks f=$stdout
|
35
|
+
puts <<EOS
|
36
|
+
Ditz has #{@descs.size} registered hooks:
|
37
|
+
|
38
|
+
EOS
|
39
|
+
|
40
|
+
@descs.map{ |k,v| [k.to_s,v] }.sort.each do |name, desc|
|
41
|
+
f.puts <<EOS
|
42
|
+
#{name}
|
43
|
+
#{"-" * name.length}
|
44
|
+
#{desc}
|
45
|
+
EOS
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def enabled? name; !hooks_for(name).empty? end
|
50
|
+
|
51
|
+
def hooks_for name
|
52
|
+
if @blocks[name].nil? || @blocks[name].empty?
|
53
|
+
dirs = [Ditz::home_dir, Ditz::find_dir_containing(".ditz")].compact.map do |d|
|
54
|
+
File.join d, ".ditz", "hooks"
|
55
|
+
end
|
56
|
+
Ditz::debug "looking for hooks in #{dirs.join(" and ")}"
|
57
|
+
files = dirs.map { |d| Dir[File.join(d, "*.rb")] }.flatten
|
58
|
+
files.each do |fn|
|
59
|
+
Ditz::debug "loading hook file #{fn}"
|
60
|
+
require File.expand_path(fn)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
@blocks[name] || []
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/ditz/html.rb
ADDED
@@ -0,0 +1,107 @@
|
|
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, links, binding={}
|
9
|
+
@template_dir = template_dir
|
10
|
+
@links = links
|
11
|
+
@binding = binding
|
12
|
+
end
|
13
|
+
|
14
|
+
## return an ErbHtml object that has the current binding plus extra_binding merged in
|
15
|
+
def clone_for_binding extra_binding={}
|
16
|
+
extra_binding.empty? ? self : ErbHtml.new(@template_dir, @links, @binding.merge(extra_binding))
|
17
|
+
end
|
18
|
+
|
19
|
+
def render_template template_name, extra_binding={}
|
20
|
+
if extra_binding.empty?
|
21
|
+
@@erbs ||= {}
|
22
|
+
@@erbs[template_name] ||= ERB.new IO.read(File.join(@template_dir, "#{template_name}.rhtml"))
|
23
|
+
@@erbs[template_name].result binding
|
24
|
+
else
|
25
|
+
clone_for_binding(extra_binding).render_template template_name
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def render_string s, extra_binding={}
|
30
|
+
if extra_binding.empty?
|
31
|
+
ERB.new(s).result binding
|
32
|
+
else
|
33
|
+
clone_for_binding(extra_binding).render_string s
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
###
|
38
|
+
### the following methods are meant to be called from the ERB itself
|
39
|
+
###
|
40
|
+
|
41
|
+
def h o; o.to_s.gsub("&", "&").gsub("<", "<").gsub(">", ">") end
|
42
|
+
def t o; o.strftime "%Y-%m-%d %H:%M %Z" end
|
43
|
+
def p o; "<p>" + h(o.to_s).gsub("\n\n", "</p><p>") + "</p>" end
|
44
|
+
def obscured_email e; h e.gsub(/@.*?(>|$)/, "@...\\1") end
|
45
|
+
def link_to o, name
|
46
|
+
dest = @links[o]
|
47
|
+
dest = o if dest.nil? && o.is_a?(String)
|
48
|
+
raise ArgumentError, "no link for #{o.inspect}" unless dest
|
49
|
+
"<a href=\"#{dest}\">#{name}</a>"
|
50
|
+
end
|
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(" ") + "/>"
|
71
|
+
end
|
72
|
+
|
73
|
+
def issue_link_for i, opts={}
|
74
|
+
link = if opts[:inline]
|
75
|
+
"<span class=\"inline-issue-link\">" + link_to(i, "issue <span class=\"id\">#{i.id[0,8]}</span>: #{i.title}") + "</span>"
|
76
|
+
else
|
77
|
+
link_to i, i.title
|
78
|
+
end
|
79
|
+
link = link + " " + issue_status_img_for(i, :class => "inline-status-image") if opts[:status_image]
|
80
|
+
link
|
81
|
+
end
|
82
|
+
|
83
|
+
def link_issue_names project, s, opts={}
|
84
|
+
project.issues.inject(s) do |s, i|
|
85
|
+
s.gsub(/\b#{i.name}\b/, issue_link_for(i, {:inline => true, :status_image => true}.merge(opts)))
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def progress_meter p, size=50
|
90
|
+
done = (p * size).to_i
|
91
|
+
undone = [size - done, 0].max
|
92
|
+
"<span class='progress-meter'><span class='progress-meter-done'>" +
|
93
|
+
(" " * done) +
|
94
|
+
"</span><span class='progress-meter-undone'>" +
|
95
|
+
(" " * undone) +
|
96
|
+
"</span></span>"
|
97
|
+
end
|
98
|
+
|
99
|
+
## render a nested ERB
|
100
|
+
alias :render :render_template
|
101
|
+
|
102
|
+
def method_missing meth, *a
|
103
|
+
@binding.member?(meth) ? @binding[meth] : super
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|