| |   |
| 1 | 1 | #!/usr/bin/env ruby |
| 2 | 2 | |
| 3 | | # requires are splitted in two for efficiency reasons |
| 4 | | # ditz should be really fast when using it for |
| 5 | | # completion. |
| 3 | ## requires are split in two for efficiency reasons: ditz should be really |
| 4 | ## fast when using it for completion. |
| 6 | 5 | require 'operator' |
| 7 | | |
| 8 | 6 | op = Ditz::Operator.new |
| 9 | 7 | |
| 10 | | # a secret option for shell completion |
| 8 | ## a secret option for shell completion |
| 11 | 9 | if ARGV.include? '--commands' |
| 12 | 10 | puts op.class.operations.map { |name, _| name } |
| 13 | 11 | exit 0 |
| … | … | |
| 13 | 13 | |
| 14 | 14 | require 'rubygems' |
| 15 | 15 | require 'fileutils' |
| 16 | require 'pathname' |
| 16 | 17 | require 'trollop'; include Trollop |
| 17 | 18 | require "ditz" |
| 18 | 19 | |
| 19 | 20 | PROJECT_FN = "project.yaml" |
| 20 | 21 | CONFIG_FN = ".ditz-config" |
| 22 | PLUGIN_FN = ".ditz-plugins" |
| 23 | |
| 24 | ## helper for recursive search |
| 25 | def find_dir_containing target, start=Pathname.new(".") |
| 26 | return start if (start + target).exist? |
| 27 | unless start.parent.realpath == start.realpath |
| 28 | find_dir_containing target, start.parent |
| 29 | end |
| 30 | end |
| 31 | |
| 32 | ## my brilliant solution to the 'gem datadir' problem |
| 33 | def find_ditz_file fn |
| 34 | dir = $:.find { |p| File.exist? File.expand_path(File.join(p, fn)) } |
| 35 | raise "can't find #{fn} in any load path" unless dir |
| 36 | File.expand_path File.join(dir, fn) |
| 37 | end |
| 38 | |
| 39 | config_dir = find_dir_containing CONFIG_FN |
| 40 | plugin_dir = find_dir_containing PLUGIN_FN |
| 21 | 41 | |
| 22 | 42 | $opts = options do |
| 23 | 43 | version "ditz #{Ditz::VERSION}" |
| 24 | 44 | opt :issue_dir, "Issue database dir", :default => "bugs" |
| 25 | | opt :config_file, "Configuration file", :default => File.join(ENV["HOME"], CONFIG_FN) |
| 45 | opt :config_file, "Configuration file", :default => File.join(config_dir || ".", CONFIG_FN) |
| 46 | opt :plugins_file, "Plugins file", :default => File.join(plugin_dir || ".", PLUGIN_FN) |
| 26 | 47 | opt :verbose, "Verbose output", :default => false |
| 27 | 48 | opt :no_comment, "Skip asking for a comment", :default => false |
| 28 | 49 | opt :list_hooks, "List all hooks and descriptions, and quit.", :short => 'l', :default => false |
| … | … | |
| 87 | 87 | Ditz::HookManager.print_hooks |
| 88 | 88 | exit 0 |
| 89 | 89 | end |
| 90 | | cmd = ARGV.shift or die "expecting a ditz command" |
| 91 | | dir = Pathname.new($opts[:issue_dir]) |
| 92 | | Ditz::Issue::ISSUE_DIR = dir |
| 93 | 90 | |
| 94 | | case cmd # some special cases not handled by Ditz::Operator |
| 91 | plugins = begin |
| 92 | Ditz::debug "loading plugins from #{$opts[:plugins_file]}" |
| 93 | YAML::load_file$opts[:plugins_file] |
| 94 | rescue SystemCallError => e |
| 95 | Ditz::debug "can't load plugins file: #{e.message}" |
| 96 | [] |
| 97 | end |
| 98 | |
| 99 | plugins.each do |p| |
| 100 | fn = find_ditz_file "plugins/#{p}.rb" |
| 101 | Ditz::debug "loading plugin #{p.inspect} from #{fn}" |
| 102 | load fn |
| 103 | end |
| 104 | |
| 105 | config = begin |
| 106 | Ditz::debug "loading config from #{$opts[:config_file]}" |
| 107 | Ditz::Config.from $opts[:config_file] |
| 108 | rescue SystemCallError => e |
| 109 | puts <<EOS |
| 110 | I wasn't able to find a configuration file #{$opts[:config_file]}. |
| 111 | We'll set it up right now. |
| 112 | EOS |
| 113 | Ditz::Config.create_interactively.save! $opts[:config_file] |
| 114 | end |
| 115 | |
| 116 | cmd = ARGV.shift || "todo" |
| 117 | issue_dir = Pathname.new(config.issue_dir || $opts[:issue_dir]) |
| 118 | |
| 119 | case cmd # some special commands not handled by Ditz::Operator |
| 95 | 120 | when "init" |
| 96 | | die "#{dir} directory already exists" if dir.exist? |
| 97 | | dir.mkdir |
| 98 | | fn = dir + PROJECT_FN |
| 121 | die "#{issue_dir} directory already exists" if issue_dir.exist? |
| 122 | issue_dir.mkdir |
| 123 | fn = issue_dir + PROJECT_FN |
| 99 | 124 | project = op.init |
| 100 | 125 | project.save! fn |
| 101 | | puts "Ok, #{dir} directory created successfully." |
| 126 | puts "Ok, #{issue_dir} directory created successfully." |
| 102 | 127 | exit |
| 103 | 128 | when "help" |
| 104 | 129 | op.do "help", nil, nil, ARGV |
| 105 | 130 | exit |
| 106 | 131 | end |
| 107 | 132 | |
| 108 | | project_root = Ditz::find_project_root Pathname.pwd, Pathname.new('.'), dir |
| 109 | | die "No #{dir} directory---use 'ditz init' to initialize" unless project_root != nil and project_root.exist? |
| 110 | | Dir.chdir project_root |
| 133 | project_root = find_dir_containing(issue_dir + PROJECT_FN) |
| 134 | die "No #{issue_dir} directory---use 'ditz init' to initialize" unless project_root |
| 135 | project_root += issue_dir |
| 111 | 136 | |
| 112 | 137 | project = begin |
| 113 | | fn = dir + PROJECT_FN |
| 138 | fn = project_root + PROJECT_FN |
| 114 | 139 | Ditz::debug "loading project from #{fn}" |
| 115 | 140 | project = Ditz::Project.from fn |
| 116 | 141 | |
| 117 | | fn = dir + "issue-*.yaml" |
| 142 | fn = project_root + "issue-*.yaml" |
| 118 | 143 | Ditz::debug "loading issues from #{fn}" |
| 119 | 144 | project.issues = Dir[fn].map { |fn| Ditz::Issue.from fn } |
| 120 | 145 | Ditz::debug "found #{project.issues.size} issues" |
| … | … | |
| 151 | 151 | project.each_modelobject { |o| o.after_deserialize project } |
| 152 | 152 | project.issues.each { |o| o.after_deserialize project } |
| 153 | 153 | project.validate! |
| 154 | project.issues.each { |p| p.project = project} |
| 154 | 155 | project.assign_issue_names! |
| 155 | 156 | |
| 156 | | config = begin |
| 157 | | if File.exists? CONFIG_FN |
| 158 | | Ditz::debug "loading local config from #{CONFIG_FN}" |
| 159 | | Ditz::Config.from CONFIG_FN |
| 160 | | else |
| 161 | | Ditz::debug "loading global config from #{$opts[:config_file]}" |
| 162 | | Ditz::Config.from $opts[:config_file] |
| 163 | | end |
| 164 | | rescue SystemCallError, Ditz::ModelObject::ModelError => e |
| 165 | | puts <<EOS |
| 166 | | I wasn't able to find a configuration file #{$opts[:config_file]}. |
| 167 | | We'll set it up right now. |
| 168 | | EOS |
| 169 | | Ditz::Config.create_interactively |
| 170 | | end |
| 171 | | |
| 172 | 157 | unless op.has_operation? cmd |
| 173 | 158 | die "no such command: #{cmd}" |
| 174 | 159 | end |
| … | … | |
| 169 | 169 | op.do cmd, project, config, args |
| 170 | 170 | rescue Ditz::Operator::Error => e |
| 171 | 171 | die e.message |
| 172 | | rescue Interrupt |
| 172 | rescue Errno::EPIPE, Interrupt |
| 173 | 173 | exit 1 |
| 174 | 174 | end |
| 175 | 175 | |
| 176 | 176 | ## save project.yaml |
| 177 | 177 | dirty = project.each_modelobject { |o| break true if o.changed? } || false |
| 178 | 178 | if dirty |
| 179 | | fn = dir + PROJECT_FN |
| 179 | fn = project_root + PROJECT_FN |
| 180 | 180 | Ditz::debug "project is dirty, saving #{fn}" |
| 181 | | project.each_modelobject { |o| o.before_serialize project } |
| 182 | 181 | project.save! fn |
| 183 | 182 | end |
| 184 | 183 | |
| 185 | | changed_issues = project.issues.select { |i| i.changed? } |
| 186 | | |
| 187 | 184 | ## project issues are not model fields proper, so they must be |
| 188 | 185 | ## saved independently. |
| 186 | changed_issues = project.issues.select { |i| i.changed? } |
| 189 | 187 | changed_issues.each do |i| |
| 190 | | i.before_serialize project |
| 191 | | fn = i.pathname |
| 192 | | Ditz::debug "issue #{i.name} is dirty, saving #{fn}" |
| 193 | | i.save! fn |
| 188 | i.pathname ||= (project_root + "issue-#{i.id}.yaml") |
| 189 | i.project ||= project # hack: not set on new issues |
| 190 | Ditz::debug "issue #{i.name} is dirty, saving #{i.pathname}" |
| 191 | i.save! i.pathname |
| 194 | 192 | end |
| 195 | 193 | |
| 196 | 194 | project.deleted_issues.each do |i| |
| toggle raw diff |
--- a/bin/ditz
+++ b/bin/ditz
@@ -1,13 +1,11 @@
#!/usr/bin/env ruby
-# requires are splitted in two for efficiency reasons
-# ditz should be really fast when using it for
-# completion.
+## requires are split in two for efficiency reasons: ditz should be really
+## fast when using it for completion.
require 'operator'
-
op = Ditz::Operator.new
-# a secret option for shell completion
+## a secret option for shell completion
if ARGV.include? '--commands'
puts op.class.operations.map { |name, _| name }
exit 0
@@ -15,16 +13,37 @@ end
require 'rubygems'
require 'fileutils'
+require 'pathname'
require 'trollop'; include Trollop
require "ditz"
PROJECT_FN = "project.yaml"
CONFIG_FN = ".ditz-config"
+PLUGIN_FN = ".ditz-plugins"
+
+## helper for recursive search
+def find_dir_containing target, start=Pathname.new(".")
+ return start if (start + target).exist?
+ unless start.parent.realpath == start.realpath
+ find_dir_containing target, start.parent
+ end
+end
+
+## my brilliant solution to the 'gem datadir' problem
+def find_ditz_file fn
+ dir = $:.find { |p| File.exist? File.expand_path(File.join(p, fn)) }
+ raise "can't find #{fn} in any load path" unless dir
+ File.expand_path File.join(dir, fn)
+end
+
+config_dir = find_dir_containing CONFIG_FN
+plugin_dir = find_dir_containing PLUGIN_FN
$opts = options do
version "ditz #{Ditz::VERSION}"
opt :issue_dir, "Issue database dir", :default => "bugs"
- opt :config_file, "Configuration file", :default => File.join(ENV["HOME"], CONFIG_FN)
+ opt :config_file, "Configuration file", :default => File.join(config_dir || ".", CONFIG_FN)
+ opt :plugins_file, "Plugins file", :default => File.join(plugin_dir || ".", PLUGIN_FN)
opt :verbose, "Verbose output", :default => false
opt :no_comment, "Skip asking for a comment", :default => false
opt :list_hooks, "List all hooks and descriptions, and quit.", :short => 'l', :default => false
@@ -68,34 +87,59 @@ if $opts[:list_hooks]
Ditz::HookManager.print_hooks
exit 0
end
-cmd = ARGV.shift or die "expecting a ditz command"
-dir = Pathname.new($opts[:issue_dir])
-Ditz::Issue::ISSUE_DIR = dir
-case cmd # some special cases not handled by Ditz::Operator
+plugins = begin
+ Ditz::debug "loading plugins from #{$opts[:plugins_file]}"
+ YAML::load_file$opts[:plugins_file]
+rescue SystemCallError => e
+ Ditz::debug "can't load plugins file: #{e.message}"
+ []
+end
+
+plugins.each do |p|
+ fn = find_ditz_file "plugins/#{p}.rb"
+ Ditz::debug "loading plugin #{p.inspect} from #{fn}"
+ load fn
+end
+
+config = begin
+ Ditz::debug "loading config from #{$opts[:config_file]}"
+ Ditz::Config.from $opts[:config_file]
+rescue SystemCallError => e
+ puts <<EOS
+I wasn't able to find a configuration file #{$opts[:config_file]}.
+We'll set it up right now.
+EOS
+ Ditz::Config.create_interactively.save! $opts[:config_file]
+end
+
+cmd = ARGV.shift || "todo"
+issue_dir = Pathname.new(config.issue_dir || $opts[:issue_dir])
+
+case cmd # some special commands not handled by Ditz::Operator
when "init"
- die "#{dir} directory already exists" if dir.exist?
- dir.mkdir
- fn = dir + PROJECT_FN
+ die "#{issue_dir} directory already exists" if issue_dir.exist?
+ issue_dir.mkdir
+ fn = issue_dir + PROJECT_FN
project = op.init
project.save! fn
- puts "Ok, #{dir} directory created successfully."
+ puts "Ok, #{issue_dir} directory created successfully."
exit
when "help"
op.do "help", nil, nil, ARGV
exit
end
-project_root = Ditz::find_project_root Pathname.pwd, Pathname.new('.'), dir
-die "No #{dir} directory---use 'ditz init' to initialize" unless project_root != nil and project_root.exist?
-Dir.chdir project_root
+project_root = find_dir_containing(issue_dir + PROJECT_FN)
+die "No #{issue_dir} directory---use 'ditz init' to initialize" unless project_root
+project_root += issue_dir
project = begin
- fn = dir + PROJECT_FN
+ fn = project_root + PROJECT_FN
Ditz::debug "loading project from #{fn}"
project = Ditz::Project.from fn
- fn = dir + "issue-*.yaml"
+ fn = project_root + "issue-*.yaml"
Ditz::debug "loading issues from #{fn}"
project.issues = Dir[fn].map { |fn| Ditz::Issue.from fn }
Ditz::debug "found #{project.issues.size} issues"
@@ -107,24 +151,9 @@ end
project.each_modelobject { |o| o.after_deserialize project }
project.issues.each { |o| o.after_deserialize project }
project.validate!
+project.issues.each { |p| p.project = project}
project.assign_issue_names!
-config = begin
- if File.exists? CONFIG_FN
- Ditz::debug "loading local config from #{CONFIG_FN}"
- Ditz::Config.from CONFIG_FN
- else
- Ditz::debug "loading global config from #{$opts[:config_file]}"
- Ditz::Config.from $opts[:config_file]
- end
-rescue SystemCallError, Ditz::ModelObject::ModelError => e
- puts <<EOS
-I wasn't able to find a configuration file #{$opts[:config_file]}.
-We'll set it up right now.
-EOS
- Ditz::Config.create_interactively
-end
-
unless op.has_operation? cmd
die "no such command: #{cmd}"
end
@@ -140,28 +169,26 @@ begin
op.do cmd, project, config, args
rescue Ditz::Operator::Error => e
die e.message
-rescue Interrupt
+rescue Errno::EPIPE, Interrupt
exit 1
end
## save project.yaml
dirty = project.each_modelobject { |o| break true if o.changed? } || false
if dirty
- fn = dir + PROJECT_FN
+ fn = project_root + PROJECT_FN
Ditz::debug "project is dirty, saving #{fn}"
- project.each_modelobject { |o| o.before_serialize project }
project.save! fn
end
-changed_issues = project.issues.select { |i| i.changed? }
-
## project issues are not model fields proper, so they must be
## saved independently.
+changed_issues = project.issues.select { |i| i.changed? }
changed_issues.each do |i|
- i.before_serialize project
- fn = i.pathname
- Ditz::debug "issue #{i.name} is dirty, saving #{fn}"
- i.save! fn
+ i.pathname ||= (project_root + "issue-#{i.id}.yaml")
+ i.project ||= project # hack: not set on new issues
+ Ditz::debug "issue #{i.name} is dirty, saving #{i.pathname}"
+ i.save! i.pathname
end
project.deleted_issues.each do |i| |
| |   |
| 1 | 1 | module Ditz |
| 2 | | module HookManager |
| 3 | | module_function |
| 2 | class HookManager |
| 3 | def initialize |
| 4 | @descs = {} |
| 5 | @blocks = {} |
| 6 | end |
| 4 | 7 | |
| 5 | | @@descs = {} |
| 6 | | @@blocks = {} |
| 8 | @@instance = nil |
| 9 | def self.method_missing m, *a, &b |
| 10 | @@instance ||= self.new |
| 11 | @@instance.send m, *a, &b |
| 12 | end |
| 7 | 13 | |
| 8 | 14 | def register name, desc |
| 9 | | raise "Ditz::HookManager.register needs a symbol not #{name.inspect}" unless name.is_a? Symbol |
| 10 | | @@descs[name] = desc |
| 11 | | @@blocks[name] = [] |
| 15 | @descs[name] = desc |
| 16 | @blocks[name] = [] |
| 12 | 17 | end |
| 13 | 18 | |
| 14 | 19 | def on *names, &block |
| 15 | | for name in names do |
| 16 | | raise "unregistered hook #{name.inspect}" unless @@descs[name] |
| 17 | | @@blocks[name] << block |
| 20 | names.each do |name| |
| 21 | raise "unregistered hook #{name.inspect}" unless @descs[name] |
| 22 | @blocks[name] << block |
| 18 | 23 | end |
| 19 | 24 | end |
| 20 | 25 | |
| 21 | 26 | def run name, *args |
| 22 | | raise "unregistered hook #{name.inspect}" unless @@descs[name] |
| 27 | raise "unregistered hook #{name.inspect}" unless @descs[name] |
| 23 | 28 | blocks = hooks_for name |
| 24 | 29 | return false if blocks.empty? |
| 25 | | for block in blocks do |
| 26 | | block[*args] |
| 27 | | end |
| 30 | blocks.each { |block| block[*args] } |
| 28 | 31 | true |
| 29 | 32 | end |
| 30 | 33 | |
| 31 | 34 | def print_hooks f=$stdout |
| 32 | 35 | puts <<EOS |
| 33 | | Ditz have #{@@descs.size} registered hooks: |
| 36 | Ditz has #{@descs.size} registered hooks: |
| 34 | 37 | |
| 35 | 38 | EOS |
| 36 | 39 | |
| 37 | | @@descs.map{ |k,v| [k.to_s,v] }.sort.each do |(name, desc)| |
| 40 | @descs.map{ |k,v| [k.to_s,v] }.sort.each do |name, desc| |
| 38 | 41 | f.puts <<EOS |
| 39 | 42 | #{name} |
| 40 | 43 | #{"-" * name.length} |
| … | … | |
| 49 | 49 | def enabled? name; !hooks_for(name).empty? end |
| 50 | 50 | |
| 51 | 51 | def hooks_for name |
| 52 | | if @@blocks[name].nil? || @@blocks[name].empty? |
| 52 | if @blocks[name].nil? || @blocks[name].empty? |
| 53 | 53 | fns = File.join(ENV['HOME'], '.ditz', 'hooks', '*.rb') |
| 54 | 54 | Dir[fns].each { |fn| load fn } |
| 55 | 55 | end |
| 56 | 56 | |
| 57 | | @@blocks[name] || [] |
| 57 | @blocks[name] || [] |
| 58 | 58 | end |
| 59 | 59 | end |
| 60 | | |
| 61 | 60 | end |
| toggle raw diff |
--- a/lib/hook.rb
+++ b/lib/hook.rb
@@ -1,40 +1,43 @@
module Ditz
- module HookManager
- module_function
+ class HookManager
+ def initialize
+ @descs = {}
+ @blocks = {}
+ end
- @@descs = {}
- @@blocks = {}
+ @@instance = nil
+ def self.method_missing m, *a, &b
+ @@instance ||= self.new
+ @@instance.send m, *a, &b
+ end
def register name, desc
- raise "Ditz::HookManager.register needs a symbol not #{name.inspect}" unless name.is_a? Symbol
- @@descs[name] = desc
- @@blocks[name] = []
+ @descs[name] = desc
+ @blocks[name] = []
end
def on *names, &block
- for name in names do
- raise "unregistered hook #{name.inspect}" unless @@descs[name]
- @@blocks[name] << block
+ names.each do |name|
+ raise "unregistered hook #{name.inspect}" unless @descs[name]
+ @blocks[name] << block
end
end
def run name, *args
- raise "unregistered hook #{name.inspect}" unless @@descs[name]
+ raise "unregistered hook #{name.inspect}" unless @descs[name]
blocks = hooks_for name
return false if blocks.empty?
- for block in blocks do
- block[*args]
- end
+ blocks.each { |block| block[*args] }
true
end
def print_hooks f=$stdout
puts <<EOS
-Ditz have #{@@descs.size} registered hooks:
+Ditz has #{@descs.size} registered hooks:
EOS
- @@descs.map{ |k,v| [k.to_s,v] }.sort.each do |(name, desc)|
+ @descs.map{ |k,v| [k.to_s,v] }.sort.each do |name, desc|
f.puts <<EOS
#{name}
#{"-" * name.length}
@@ -46,13 +49,12 @@ EOS
def enabled? name; !hooks_for(name).empty? end
def hooks_for name
- if @@blocks[name].nil? || @@blocks[name].empty?
+ if @blocks[name].nil? || @blocks[name].empty?
fns = File.join(ENV['HOME'], '.ditz', 'hooks', '*.rb')
Dir[fns].each { |fn| load fn }
end
- @@blocks[name] || []
+ @blocks[name] || []
end
end
-
end |
| |   |
| 5 | 5 | ## pass through any variables needed for template generation, and add a bunch |
| 6 | 6 | ## of HTML formatting utility methods. |
| 7 | 7 | class ErbHtml |
| 8 | | def initialize template_dir, template_name, links, mapping={} |
| 9 | | @template_name = template_name |
| 8 | def initialize template_dir, links, binding={} |
| 10 | 9 | @template_dir = template_dir |
| 11 | 10 | @links = links |
| 12 | | @mapping = mapping |
| 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 |
| 13 | 28 | |
| 14 | | @@erbs ||= {} |
| 15 | | @@erbs[template_name] ||= ERB.new(IO.readlines(File.join(template_dir, "#{template_name}.rhtml")).join) |
| 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 |
| 16 | 35 | end |
| 17 | 36 | |
| 37 | ### |
| 38 | ### the following methods are meant to be called from the ERB itself |
| 39 | ### |
| 40 | |
| 18 | 41 | def h o; o.to_s.gsub("&", "&").gsub("<", "<").gsub(">", ">") end |
| 42 | def t o; o.strftime "%Y-%m-%d %H:%M %Z" end |
| 19 | 43 | def p o; "<p>" + h(o.to_s).gsub("\n\n", "</p><p>") + "</p>" end |
| 20 | 44 | def obscured_email e; h e.gsub(/@.*?(>|$)/, "@...\\1") end |
| 21 | 45 | def link_to o, name |
| … | … | |
| 49 | 49 | "<a href=\"#{dest}\">#{name}</a>" |
| 50 | 50 | end |
| 51 | 51 | |
| 52 | | def render template_name, morevars={} |
| 53 | | ErbHtml.new(@template_dir, template_name, @links, @mapping.merge(morevars)).to_s |
| 52 | def link_issue_names project, s |
| 53 | project.issues.inject(s) do |s, i| |
| 54 | s.gsub(/\b#{i.name}\b/, link_to(i, i.closed? ? "#{i.title} (#{i.disposition})" : i.title)) |
| 55 | end |
| 54 | 56 | end |
| 55 | 57 | |
| 56 | | def method_missing meth, *a |
| 57 | | @mapping.member?(meth) ? @mapping[meth] : super |
| 58 | | end |
| 58 | ## render a nested ERB |
| 59 | alias :render :render_template |
| 59 | 60 | |
| 60 | | def to_s |
| 61 | | @@erbs[@template_name].result binding |
| 61 | def method_missing meth, *a |
| 62 | @binding.member?(meth) ? @binding[meth] : super |
| 62 | 63 | end |
| 63 | 64 | end |
| 64 | 65 | |
| toggle raw diff |
--- a/lib/html.rb
+++ b/lib/html.rb
@@ -5,17 +5,41 @@ module Ditz
## pass through any variables needed for template generation, and add a bunch
## of HTML formatting utility methods.
class ErbHtml
- def initialize template_dir, template_name, links, mapping={}
- @template_name = template_name
+ def initialize template_dir, links, binding={}
@template_dir = template_dir
@links = links
- @mapping = mapping
+ @binding = binding
+ end
+
+ ## return an ErbHtml object that has the current binding plus extra_binding merged in
+ def clone_for_binding extra_binding={}
+ extra_binding.empty? ? self : ErbHtml.new(@template_dir, @links, @binding.merge(extra_binding))
+ end
+
+ def render_template template_name, extra_binding={}
+ if extra_binding.empty?
+ @@erbs ||= {}
+ @@erbs[template_name] ||= ERB.new IO.read(File.join(@template_dir, "#{template_name}.rhtml"))
+ @@erbs[template_name].result binding
+ else
+ clone_for_binding(extra_binding).render_template template_name
+ end
+ end
- @@erbs ||= {}
- @@erbs[template_name] ||= ERB.new(IO.readlines(File.join(template_dir, "#{template_name}.rhtml")).join)
+ def render_string s, extra_binding={}
+ if extra_binding.empty?
+ ERB.new(s).result binding
+ else
+ clone_for_binding(extra_binding).render_string s
+ end
end
+ ###
+ ### the following methods are meant to be called from the ERB itself
+ ###
+
def h o; o.to_s.gsub("&", "&").gsub("<", "<").gsub(">", ">") end
+ def t o; o.strftime "%Y-%m-%d %H:%M %Z" end
def p o; "<p>" + h(o.to_s).gsub("\n\n", "</p><p>") + "</p>" end
def obscured_email e; h e.gsub(/@.*?(>|$)/, "@...\\1") end
def link_to o, name
@@ -25,16 +49,17 @@ class ErbHtml
"<a href=\"#{dest}\">#{name}</a>"
end
- def render template_name, morevars={}
- ErbHtml.new(@template_dir, template_name, @links, @mapping.merge(morevars)).to_s
+ def link_issue_names project, s
+ project.issues.inject(s) do |s, i|
+ s.gsub(/\b#{i.name}\b/, link_to(i, i.closed? ? "#{i.title} (#{i.disposition})" : i.title))
+ end
end
- def method_missing meth, *a
- @mapping.member?(meth) ? @mapping[meth] : super
- end
+ ## render a nested ERB
+ alias :render :render_template
- def to_s
- @@erbs[@template_name].result binding
+ def method_missing meth, *a
+ @binding.member?(meth) ? @binding[meth] : super
end
end
|
| |   |
| 26 | 26 | no issues |
| 27 | 27 | <% else %> |
| 28 | 28 | <%= sprintf "%.0f%%", pct_done %> complete; |
| 29 | | <%= open_issues.map { |n,is| n.to_s.pluralize is.size }.join(' and ') %> open. |
| 29 | <% if open_issues.empty? %> |
| 30 | ready for release! |
| 31 | <% else %> |
| 32 | <%= open_issues.map { |n,is| n.to_s.pluralize is.size }.join(' and ') %> open. |
| 33 | <% end %> |
| 30 | 34 | <% end %> |
| 31 | 35 | </li> |
| 32 | 36 | <% end %> |
| … | … | |
| 54 | 54 | open_issues = issues.select { |i| i.open? } |
| 55 | 55 | %> |
| 56 | 56 | <p> |
| 57 | | <%= link_to "unassigned", "unassigned issue".pluralize(issues.size).capitalize %>; <%= open_issues.size.to_pretty_s %> open. |
| 57 | <% if issues.empty? %> |
| 58 | No unassigned issues. |
| 59 | <% else %> |
| 60 | <%= link_to "unassigned", "unassigned issue".pluralize(issues.size).capitalize %>; <%= open_issues.size.to_pretty_s %> of them open. |
| 61 | <% end %> |
| 58 | 62 | </p> |
| 59 | 63 | |
| 60 | 64 | <% if components.size > 1 %> |
| … | … | |
| 69 | 69 | open_issues = project.group_issues(project.issues_for_component(c).select { |i| i.open? }) |
| 70 | 70 | %> |
| 71 | 71 | <li> |
| 72 | | <%= link_to c, c.name %>: |
| 73 | 72 | <% if open_issues.empty? %> |
| 73 | <span class="dimmed"> |
| 74 | <%= link_to c, c.name %>: |
| 74 | 75 | no open issues. |
| 76 | </span> |
| 75 | 77 | <% else %> |
| 78 | <%= link_to c, c.name %>: |
| 76 | 79 | <%= open_issues.map { |n,is| n.to_s.pluralize is.size }.join(' and ') %> open. |
| 77 | 80 | <% end %> |
| 78 | 81 | </li> |
| … | … | |
| 83 | 83 | </ul> |
| 84 | 84 | <% end %> |
| 85 | 85 | |
| 86 | <h2>Recently modified issues</h2> |
| 87 | |
| 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 | uniq_by { |stuff, i| i }. |
| 93 | sort_by { |e| e.first.first }. |
| 94 | reverse[0 ... 10]. |
| 95 | each do |(date, author, what, comment), i| %> |
| 96 | <tr> |
| 97 | <td><%= date.pretty_date %></td> |
| 98 | <td class="issuestatus_<%= i.status %>"> |
| 99 | <% if i.closed? %> |
| 100 | <%= i.disposition_string %> |
| 101 | <% else %> |
| 102 | <%= i.status_string %> |
| 103 | <% end %> |
| 104 | </td> |
| 105 | <td class="issuename"> |
| 106 | <%= link_to i, i.title %> |
| 107 | <%= i.bug? ? '(bug)' : '' %> |
| 108 | </td> |
| 109 | <td><%= what %></td> |
| 110 | </tr> |
| 111 | <% end %> |
| 112 | </table> |
| 113 | |
| 86 | 114 | <p class="footer">Generated by <a href="http://ditz.rubyforge.org/">ditz</a>. |
| 87 | 115 | |
| 88 | 116 | </body> |
| toggle raw diff |
--- a/lib/index.rhtml
+++ b/lib/index.rhtml
@@ -26,7 +26,11 @@
no issues
<% else %>
<%= sprintf "%.0f%%", pct_done %> complete;
- <%= open_issues.map { |n,is| n.to_s.pluralize is.size }.join(' and ') %> open.
+ <% if open_issues.empty? %>
+ ready for release!
+ <% else %>
+ <%= open_issues.map { |n,is| n.to_s.pluralize is.size }.join(' and ') %> open.
+ <% end %>
<% end %>
</li>
<% end %>
@@ -50,7 +54,11 @@
open_issues = issues.select { |i| i.open? }
%>
<p>
- <%= link_to "unassigned", "unassigned issue".pluralize(issues.size).capitalize %>; <%= open_issues.size.to_pretty_s %> open.
+ <% if issues.empty? %>
+ No unassigned issues.
+ <% else %>
+ <%= link_to "unassigned", "unassigned issue".pluralize(issues.size).capitalize %>; <%= open_issues.size.to_pretty_s %> of them open.
+ <% end %>
</p>
<% if components.size > 1 %>
@@ -61,10 +69,13 @@
open_issues = project.group_issues(project.issues_for_component(c).select { |i| i.open? })
%>
<li>
- <%= link_to c, c.name %>:
<% if open_issues.empty? %>
+ <span class="dimmed">
+ <%= link_to c, c.name %>:
no open issues.
+ </span>
<% else %>
+ <%= link_to c, c.name %>:
<%= open_issues.map { |n,is| n.to_s.pluralize is.size }.join(' and ') %> open.
<% end %>
</li>
@@ -72,6 +83,34 @@
</ul>
<% end %>
+<h2>Recently modified issues</h2>
+
+<table>
+<% project.issues.map { |i| i.log_events.map { |e| [e, i] } }.
+ flatten_one_level.
+ sort_by { |e| e.first.first }.
+ uniq_by { |stuff, i| i }.
+ sort_by { |e| e.first.first }.
+ reverse[0 ... 10].
+ each do |(date, author, what, comment), i| %>
+ <tr>
+ <td><%= date.pretty_date %></td>
+ <td class="issuestatus_<%= i.status %>">
+ <% if i.closed? %>
+ <%= i.disposition_string %>
+ <% else %>
+ <%= i.status_string %>
+ <% end %>
+ </td>
+ <td class="issuename">
+ <%= link_to i, i.title %>
+ <%= i.bug? ? '(bug)' : '' %>
+ </td>
+ <td><%= what %></td>
+ </tr>
+<% end %>
+</table>
+
<p class="footer">Generated by <a href="http://ditz.rubyforge.org/">ditz</a>.
</body> |