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/lib/ditz/views.rb
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
require "ditz/view"
|
2
|
+
require "ditz/html"
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module Ditz
|
6
|
+
|
7
|
+
class ScreenView < View
|
8
|
+
def initialize project, config, device=$stdout
|
9
|
+
@device = device
|
10
|
+
@config = config
|
11
|
+
end
|
12
|
+
|
13
|
+
def format_log_events events
|
14
|
+
return "none" if events.empty?
|
15
|
+
events.reverse.map do |time, who, what, comment|
|
16
|
+
"- #{what} (#{who.shortened_email}, #{time.ago} ago)" +
|
17
|
+
(comment =~ /\S/ ? "\n" + comment.gsub(/^/, " > ") : "")
|
18
|
+
end.join("\n")
|
19
|
+
end
|
20
|
+
private :format_log_events
|
21
|
+
|
22
|
+
def render_issue issue
|
23
|
+
status = case issue.status
|
24
|
+
when :closed
|
25
|
+
"#{issue.status_string}: #{issue.disposition_string}"
|
26
|
+
else
|
27
|
+
issue.status_string
|
28
|
+
end
|
29
|
+
desc = if issue.desc.size < 80 - "Description: ".length
|
30
|
+
issue.desc
|
31
|
+
else
|
32
|
+
"\n" + issue.desc.gsub(/^/, " ") + "\n"
|
33
|
+
end
|
34
|
+
run_pager @config
|
35
|
+
@device.puts <<EOS
|
36
|
+
#{"Issue #{issue.name}".underline}
|
37
|
+
Title: #{issue.title}
|
38
|
+
Description: #{desc}
|
39
|
+
Type: #{issue.type}
|
40
|
+
Status: #{status}
|
41
|
+
Creator: #{issue.reporter}
|
42
|
+
Age: #{issue.creation_time.ago}
|
43
|
+
Release: #{issue.release}
|
44
|
+
References: #{issue.references.listify " "}
|
45
|
+
Identifier: #{issue.id}
|
46
|
+
EOS
|
47
|
+
|
48
|
+
self.class.view_additions_for(:issue_summary).each { |b| @device.print(b[issue, @config] || next) }
|
49
|
+
puts
|
50
|
+
self.class.view_additions_for(:issue_details).each { |b| @device.print(b[issue, @config] || next) }
|
51
|
+
|
52
|
+
@device.puts <<EOS
|
53
|
+
Event log:
|
54
|
+
#{format_log_events issue.log_events}
|
55
|
+
EOS
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class HtmlView < View
|
60
|
+
SUPPORT_FILES = %w(style.css blue-check.png red-check.png green-check.png green-bar.png yellow-bar.png)
|
61
|
+
|
62
|
+
def initialize project, config, dir
|
63
|
+
@project = project
|
64
|
+
@config = config
|
65
|
+
@dir = dir
|
66
|
+
@template_dir = File.dirname Ditz::find_ditz_file("../share/ditz/index.rhtml")
|
67
|
+
end
|
68
|
+
|
69
|
+
def render_all
|
70
|
+
Dir.mkdir @dir unless File.exists? @dir
|
71
|
+
SUPPORT_FILES.each { |f| FileUtils.cp File.join(@template_dir, f), @dir }
|
72
|
+
|
73
|
+
## build up links
|
74
|
+
links = {}
|
75
|
+
@project.releases.each { |r| links[r] = "release-#{r.name}.html" }
|
76
|
+
@project.issues.each { |i| links[i] = "issue-#{i.id}.html" }
|
77
|
+
@project.components.each { |c| links[c] = "component-#{c.name}.html" }
|
78
|
+
links["unassigned"] = "unassigned.html" # special case: unassigned
|
79
|
+
links["index"] = "index.html" # special case: index
|
80
|
+
|
81
|
+
@project.issues.each do |issue|
|
82
|
+
fn = File.join @dir, links[issue]
|
83
|
+
#puts "Generating #{fn}..."
|
84
|
+
|
85
|
+
extra_summary = self.class.view_additions_for(:issue_summary).map { |b| b[issue, @config] }.compact
|
86
|
+
extra_details = self.class.view_additions_for(:issue_details).map { |b| b[issue, @config] }.compact
|
87
|
+
|
88
|
+
erb = ErbHtml.new(@template_dir, links, :issue => issue,
|
89
|
+
:release => (issue.release ? @project.release_for(issue.release) : nil),
|
90
|
+
:component => @project.component_for(issue.component),
|
91
|
+
:project => @project)
|
92
|
+
|
93
|
+
extra_summary_html = extra_summary.map { |string, extra_binding| erb.render_string string, extra_binding }.join
|
94
|
+
extra_details_html = extra_details.map { |string, extra_binding| erb.render_string string, extra_binding }.join
|
95
|
+
|
96
|
+
File.open(fn, "w") { |f| f.puts erb.render_template("issue", { :extra_summary_html => extra_summary_html, :extra_details_html => extra_details_html }) }
|
97
|
+
end
|
98
|
+
|
99
|
+
@project.releases.each do |r|
|
100
|
+
fn = File.join @dir, links[r]
|
101
|
+
#puts "Generating #{fn}..."
|
102
|
+
File.open(fn, "w") do |f|
|
103
|
+
f.puts ErbHtml.new(@template_dir, links, :release => r,
|
104
|
+
:issues => @project.issues_for_release(r), :project => @project).
|
105
|
+
render_template("release")
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
@project.components.each do |c|
|
110
|
+
fn = File.join @dir, links[c]
|
111
|
+
#puts "Generating #{fn}..."
|
112
|
+
File.open(fn, "w") do |f|
|
113
|
+
f.puts ErbHtml.new(@template_dir, links, :component => c,
|
114
|
+
:issues => @project.issues_for_component(c), :project => @project).
|
115
|
+
render_template("component")
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
fn = File.join @dir, links["unassigned"]
|
120
|
+
#puts "Generating #{fn}..."
|
121
|
+
File.open(fn, "w") do |f|
|
122
|
+
f.puts ErbHtml.new(@template_dir, links,
|
123
|
+
:issues => @project.unassigned_issues, :project => @project).
|
124
|
+
render_template("unassigned")
|
125
|
+
end
|
126
|
+
|
127
|
+
past_rels, upcoming_rels = @project.releases.partition { |r| r.released? }
|
128
|
+
fn = File.join @dir, links["index"]
|
129
|
+
#puts "Generating #{fn}..."
|
130
|
+
File.open(fn, "w") do |f|
|
131
|
+
f.puts ErbHtml.new(@template_dir, links, :project => @project,
|
132
|
+
:past_releases => past_rels, :upcoming_releases => upcoming_rels,
|
133
|
+
:components => @project.components).
|
134
|
+
render_template("index")
|
135
|
+
end
|
136
|
+
puts "Local generated URL: file://#{File.expand_path(fn)}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class BaetleView < View
|
141
|
+
def initialize project, config, dir
|
142
|
+
@project = project
|
143
|
+
@config = config
|
144
|
+
@dir = dir
|
145
|
+
end
|
146
|
+
|
147
|
+
def render_all
|
148
|
+
Dir.mkdir @dir unless File.exists? @dir
|
149
|
+
fn = File.join @dir, "baetle.rdf"
|
150
|
+
File.open(fn, "w") { |f|
|
151
|
+
f.puts <<EOS
|
152
|
+
@prefix baetle: <http://xmlns.com/baetle/#> .
|
153
|
+
@prefix wf: <http://www.w3.org/2005/01/wf/flow#> .
|
154
|
+
@prefix sioc: <http://rdfs.org/sioc/ns#> .
|
155
|
+
@prefix dc: <http://purl.org/dc/elements/1.1/> .
|
156
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
|
157
|
+
@prefix : <#> .
|
158
|
+
|
159
|
+
EOS
|
160
|
+
@project.issues.each do |issue|
|
161
|
+
# id
|
162
|
+
f.print ":#{issue.id} a "
|
163
|
+
f.print case issue.type
|
164
|
+
when :bugfix, :bug; "baetle:Bug"
|
165
|
+
when :feature; "baetle:Enhancement"
|
166
|
+
when :task; "wf:Task"
|
167
|
+
end
|
168
|
+
f.puts " ;"
|
169
|
+
# title
|
170
|
+
f.puts " baetle:title #{issue.title.dump} ;"
|
171
|
+
# summary
|
172
|
+
f.puts " baetle:description #{issue.desc.dump} ; "
|
173
|
+
# state
|
174
|
+
f.print " wf:state baetle:"
|
175
|
+
f.print case issue.status
|
176
|
+
when :unstarted; "New"
|
177
|
+
when :in_progress; "Started"
|
178
|
+
when :closed; "Closed"
|
179
|
+
when :paused; "Later"
|
180
|
+
end
|
181
|
+
f.puts " ;"
|
182
|
+
# created
|
183
|
+
f.puts " baetle:created #{issue.creation_time.xmlschema.dump}^^xsd:dateTime ."
|
184
|
+
f.puts
|
185
|
+
end
|
186
|
+
}
|
187
|
+
puts "Local generated URL: file://#{File.expand_path(fn)}"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
data/lib/ditz.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'trollop'
|
3
|
+
|
4
|
+
module Ditz
|
5
|
+
|
6
|
+
VERSION = "0.5"
|
7
|
+
attr_accessor :verbose
|
8
|
+
module_function :verbose, :verbose=
|
9
|
+
|
10
|
+
def debug s
|
11
|
+
puts "# #{s}" if $verbose || Ditz::verbose
|
12
|
+
end
|
13
|
+
module_function :debug
|
14
|
+
|
15
|
+
def self.has_readline?
|
16
|
+
@has_readline
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.has_readline= val
|
20
|
+
@has_readline = val
|
21
|
+
end
|
22
|
+
|
23
|
+
begin
|
24
|
+
Ditz::has_readline = false
|
25
|
+
require 'readline'
|
26
|
+
Ditz::has_readline = true
|
27
|
+
rescue LoadError
|
28
|
+
# do nothing
|
29
|
+
end
|
30
|
+
|
31
|
+
def home_dir
|
32
|
+
@home ||=
|
33
|
+
ENV["HOME"] || (ENV["HOMEDRIVE"] && ENV["HOMEPATH"] ? ENV["HOMEDRIVE"] + ENV["HOMEPATH"] : nil) || begin
|
34
|
+
$stderr.puts "warning: can't determine home directory, using '.'"
|
35
|
+
"."
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
## helper for recursive search
|
40
|
+
def find_dir_containing target, start=Pathname.new(".")
|
41
|
+
return start if (start + target).exist?
|
42
|
+
unless start.parent.realpath == start.realpath
|
43
|
+
find_dir_containing target, start.parent
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
## my brilliant solution to the 'gem datadir' problem
|
48
|
+
def find_ditz_file fn
|
49
|
+
dir = $:.find { |p| File.exist? File.expand_path(File.join(p, fn)) }
|
50
|
+
raise LoadError, "can't find #{fn} in any load path" unless dir
|
51
|
+
File.expand_path File.join(dir, fn)
|
52
|
+
end
|
53
|
+
|
54
|
+
def load_plugins fn
|
55
|
+
Ditz::debug "loading plugins from #{fn}"
|
56
|
+
return unless File.exist?(fn)
|
57
|
+
plugins = YAML::load_file fn
|
58
|
+
plugins.each do |p|
|
59
|
+
fn = Ditz::find_ditz_file "ditz/plugins/#{p}.rb"
|
60
|
+
Ditz::debug "loading plugin #{p.inspect} from #{fn}"
|
61
|
+
require File.expand_path(fn)
|
62
|
+
end
|
63
|
+
plugins
|
64
|
+
end
|
65
|
+
|
66
|
+
module_function :home_dir, :find_dir_containing, :find_ditz_file, :load_plugins
|
67
|
+
end
|
68
|
+
|
69
|
+
# Git-style automatic pagination of all output.
|
70
|
+
# Call run_pager from any opperator needing pagination.
|
71
|
+
# Yoinked from http://nex-3.com/posts/73-git-style-automatic-paging-in-ruby#comments
|
72
|
+
def run_pager config
|
73
|
+
if RUBY_VERSION >= '1.9.0'
|
74
|
+
return if RUBY_PLATFORM =~ /win32/
|
75
|
+
else
|
76
|
+
return if PLATFORM =~ /win32/
|
77
|
+
end
|
78
|
+
return unless STDOUT.tty?
|
79
|
+
return if config.paginate == 'never'
|
80
|
+
|
81
|
+
read, write = IO.pipe
|
82
|
+
|
83
|
+
unless Kernel.fork # Child process
|
84
|
+
STDOUT.reopen(write)
|
85
|
+
STDERR.reopen(write) if STDERR.tty?
|
86
|
+
read.close
|
87
|
+
write.close
|
88
|
+
return
|
89
|
+
end
|
90
|
+
|
91
|
+
# Parent process, become pager
|
92
|
+
STDIN.reopen(read)
|
93
|
+
read.close
|
94
|
+
write.close
|
95
|
+
|
96
|
+
if config.paginate == 'auto'
|
97
|
+
ENV['LESS'] = '' unless ENV['LESS'] # += doesn't work on undefined var
|
98
|
+
ENV['LESS'] += 'FRX' # Don't page if the input is short enough
|
99
|
+
end
|
100
|
+
|
101
|
+
Kernel.select [STDIN] # Wait until we have input before we start the pager
|
102
|
+
pager = ENV['PAGER'] || 'less'
|
103
|
+
exec pager rescue exec "/bin/sh", "-c", pager
|
104
|
+
end
|
105
|
+
|
106
|
+
require 'ditz/model-objects'
|
107
|
+
require 'ditz/operator'
|
108
|
+
require 'ditz/views'
|
109
|
+
require 'ditz/hook'
|
110
|
+
require 'ditz/file-storage'
|
data/man/man1/ditz.1
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
.TH "ditz" "1" "0.5" "" ""
|
2
|
+
.SH "NAME"
|
3
|
+
ditz \- simple, light\-weight distributed issue tracker
|
4
|
+
.SH "SYNOPSIS"
|
5
|
+
\fBditz\fR [ \fIoptions\fR ] \fIcommand\fR [ \fIarguments\fR ]
|
6
|
+
|
7
|
+
To list all available commands, use \fBditz help\fR. To get help for a specific command, use \fBditz help
|
8
|
+
command\fR.
|
9
|
+
.SH "DESCRIPTION"
|
10
|
+
Ditz is a simple, light\-weight distributed issue tracker designed to work with
|
11
|
+
distributed version control systems like darcs and git. Ditz maintains an issue
|
12
|
+
database directory on disk, with files written in a line\-based and human\-
|
13
|
+
editable format. This directory is kept under version control alongside
|
14
|
+
project code. Changes in issue state is handled by version control like code
|
15
|
+
change: included as part of a commit, merged with changes from other
|
16
|
+
developers, conflict\-resolved in the standard manner, etc.
|
17
|
+
|
18
|
+
Ditz provides a simple, console\-based interface for creating and updating the
|
19
|
+
issue database files, and some rudimentary HTML generation capabilities for
|
20
|
+
producing world\-readable status pages. It offers no central public method of
|
21
|
+
bug submission.
|
22
|
+
.SH "AUTHOR"
|
23
|
+
ditz was written by William Morgan <\fIwmorgan\-ditz@masanjin.net\fR>.
|
24
|
+
|
25
|
+
This manpage was written for the Debian package of ditz by Christian Garbs
|
26
|
+
<\fIdebian@cgarbs.de\fR>.
|
27
|
+
.SH "LICENSE"
|
28
|
+
Copyright (c) 2008 William Morgan.
|
29
|
+
|
30
|
+
This program is free software: you can redistribute it and/or modify
|
31
|
+
it under the terms of the GNU General Public License as published by
|
32
|
+
the Free Software Foundation, either version 3 of the License, or
|
33
|
+
(at your option) any later version.
|
34
|
+
|
35
|
+
This program is distributed in the hope that it will be useful,
|
36
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
37
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
38
|
+
GNU General Public License for more details.
|