Tracker 0.1.2 → 1.0.0
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/README +45 -18
- data/Rakefile +52 -0
- data/bin/trac +69 -52
- data/lib/comment.rb +7 -0
- data/lib/environment.rb +31 -0
- data/lib/ticket.rb +38 -0
- data/lib/tracker.rb +127 -0
- metadata +22 -53
- data/MIT-LICENSE +0 -9
- data/db/migrate +0 -70
- data/lib/action.rb +0 -220
- data/lib/bug.rb +0 -63
- data/lib/bug_description.rb +0 -15
- data/lib/database.rb +0 -44
- data/lib/helper.rb +0 -16
- data/lib/interface.rb +0 -175
- data/lib/prompter.rb +0 -66
- data/test/test_action.rb +0 -147
- data/test/test_bug.rb +0 -67
- data/test/test_bug_description.rb +0 -33
- data/test/test_integration.rb +0 -0
- data/test/test_interface.rb +0 -17
data/README
CHANGED
@@ -1,18 +1,45 @@
|
|
1
|
-
==
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
1
|
+
== What is Tracker? ==
|
2
|
+
|
3
|
+
Tracker 1.0 is a remake of Tracker (see http://tracker.rubyforge.org). It's going to be a leaner/quicker ticket tracker.
|
4
|
+
|
5
|
+
Tracker is meant for personal projects where you want to keep track of bugs that pop up or features. The database is a local sqlite3 file that can be uploaded into your Subversion repository.
|
6
|
+
|
7
|
+
This program and all software is released under the MIT License. Feel free to use it, modify it, steal from it, etc. for personal or commercial use.
|
8
|
+
|
9
|
+
|
10
|
+
== About ==
|
11
|
+
|
12
|
+
Author: Hugh Bien
|
13
|
+
Email: bienhd@gmail.com
|
14
|
+
Guide: http://recursive2.wordpress.com/2007/05/11/re-introducing-tracker/
|
15
|
+
Repository: http://recursive2.com/svn/public/tracker
|
16
|
+
Changelog: Use `svn log`
|
17
|
+
|
18
|
+
Below are checklists for the upcoming milestone and BDD checklist. The milestone checklist shows what features I'm aiming for. The BDD checklist is sort of a break down of the feature I'm currently working on.
|
19
|
+
|
20
|
+
To see a complete list of features and bugs, use `trac list` on the .tracker_db for this project.
|
21
|
+
|
22
|
+
|
23
|
+
== Version 1.1.0 Milestone :: May 20, 2007 ==
|
24
|
+
|
25
|
+
[ ] trac help 'command' should spit out help messages for each command
|
26
|
+
[ ] Extract the class 'Output' from 'Tracker'.
|
27
|
+
[ ] HTML output report
|
28
|
+
[ ] specifications for the 'trac' binary
|
29
|
+
[ ] bug fixes from v1.0.0
|
30
|
+
|
31
|
+
|
32
|
+
== BDD Checklist ==
|
33
|
+
|
34
|
+
[ ] trac help
|
35
|
+
[ ] trac help bootstrap
|
36
|
+
[ ] trac help list
|
37
|
+
[ ] trac help search
|
38
|
+
[ ] trac help create
|
39
|
+
[ ] trac help show
|
40
|
+
[ ] trac help update
|
41
|
+
[ ] trac help delete
|
42
|
+
[ ] trac help add
|
43
|
+
[ ] trac help remove
|
44
|
+
[ ] trac help open
|
45
|
+
[ ] trac help close
|
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'spec/rake/spectask'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'rake/clean'
|
5
|
+
|
6
|
+
CLEAN.include('pkg', 'doc')
|
7
|
+
|
8
|
+
gem_spec = Gem::Specification.new do |s|
|
9
|
+
s.name = "Tracker"
|
10
|
+
s.version = "1.0.0"
|
11
|
+
s.summary = "A command line ticket tracker for solo projects."
|
12
|
+
|
13
|
+
# Spec Files
|
14
|
+
s.test_files = FileList['specs/*_spec.rb']
|
15
|
+
|
16
|
+
# Executables
|
17
|
+
s.executables = ['trac']
|
18
|
+
|
19
|
+
# Other related files
|
20
|
+
s.files = FileList['README', 'Rakefile', 'lib/*.rb']
|
21
|
+
end
|
22
|
+
|
23
|
+
namespace :build do
|
24
|
+
Rake::GemPackageTask.new(gem_spec) do |pkg|
|
25
|
+
pkg.need_zip = false
|
26
|
+
pkg.need_tar = false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
namespace :spec do
|
31
|
+
desc "Runs all specifications"
|
32
|
+
Spec::Rake::SpecTask.new('all') do |t|
|
33
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "Generate specdocs for examples"
|
37
|
+
Spec::Rake::SpecTask.new('doc') do |t|
|
38
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
39
|
+
t.spec_opts = ["--format", "s"]
|
40
|
+
end
|
41
|
+
|
42
|
+
desc "Generate HTML report for specifications"
|
43
|
+
Spec::Rake::SpecTask.new('html') do |t|
|
44
|
+
`spec -f html spec/*_spec.rb > site/spec.html`
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "Runs autotest using RSpec"
|
48
|
+
task :autotest do
|
49
|
+
require './spec/rspec_autotest'
|
50
|
+
RspecAutotest.run
|
51
|
+
end
|
52
|
+
end
|
data/bin/trac
CHANGED
@@ -1,65 +1,82 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
# Please see http://ieng6.ucsd.edu/~hbien/tracker for more information about Tracker.
|
2
|
+
require File.dirname(__FILE__) + "/../lib/environment.rb"
|
4
3
|
|
5
|
-
|
4
|
+
params = {}
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
6
|
+
parser = OptionParser.new do |opts|
|
7
|
+
opts.banner = "Usage: #{$0} <subcommand> [options]\n" +
|
8
|
+
"Tracker is a ticket tracker for personal projects.\n" +
|
9
|
+
"\nAvailable Subcommands:\n" +
|
10
|
+
" bootstrap\n" +
|
11
|
+
" list (--open | --closed)\n" +
|
12
|
+
" search PHRASE\n" +
|
13
|
+
" create --message STRING --keywords STRING\n" +
|
14
|
+
" show ID\n" +
|
15
|
+
" update ID --message STRING --keywords STRING\n" +
|
16
|
+
" delete ID\n" +
|
17
|
+
" add ID --message STRING\n" +
|
18
|
+
" remove ID --position INT\n" +
|
19
|
+
" open ID\n" +
|
20
|
+
" close ID\n\n"
|
21
|
+
|
22
|
+
opts.on("-m", "--message [STRING]", "sets the message of your ticket or comment") do |message|
|
23
|
+
params[:message] = message
|
24
|
+
end
|
26
25
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
opts.on("-k", "--keywords [STRING]", "sets the keywords of your ticket") do |keywords|
|
27
|
+
params[:keywords] = keywords
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.on("-i", "--id [INT]", "sets the id of the ticket you want") do |id|
|
31
|
+
params[:id] = id
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on("-p", "--position [INT]", "position of a comment") do |position|
|
35
|
+
params[:position] = position
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on("-l", "--limit [INT]", "limit the number of tickets to list or search for") do |limit|
|
39
|
+
params[:limit] = limit
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on("-o", "--open", "list open tickets only") do
|
43
|
+
params[:state] = Ticket::OPEN
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on("-c", "--closed", "list closed tickets only") do
|
47
|
+
params[:state] = Ticket::CLOSED
|
48
|
+
end
|
49
|
+
|
50
|
+
opts.on("-h", "--help", "display this help menu and exit") do
|
51
|
+
puts opts
|
31
52
|
exit
|
32
|
-
|
33
|
-
|
53
|
+
end
|
54
|
+
|
55
|
+
opts.on("-v", "--version", "shows version of Tracker (v1.0.0)") do
|
56
|
+
puts "Tracker v1.0.0"
|
34
57
|
exit
|
35
58
|
end
|
36
59
|
end
|
37
60
|
|
38
|
-
#
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
61
|
+
# For unknown commands
|
62
|
+
unless %w[bootstrap list search create show update delete add remove open close -h --help -v --version].include?(ARGV[0])
|
63
|
+
puts "Unknown command: '#{ARGV[0]}'\n" +
|
64
|
+
"Type '#{$0} --help' for usage."
|
65
|
+
exit
|
43
66
|
end
|
44
67
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
database = File.join(database, ".tracker_db") if File.directory?(database)
|
54
|
-
no_database_error(database) if !File.exist?(database)
|
55
|
-
end
|
56
|
-
# if we did, execute command
|
57
|
-
Database::connect_to_database(database)
|
58
|
-
parser = Interface.new
|
59
|
-
begin
|
60
|
-
parser.parse_command(ARGV)
|
61
|
-
rescue Exception => error
|
62
|
-
puts "Error executing database instructions: " + error
|
68
|
+
begin
|
69
|
+
# Runs the application
|
70
|
+
parser.parse!(ARGV)
|
71
|
+
tracker = Tracker.new
|
72
|
+
|
73
|
+
# Turn ID into ARGV[1] if necessary
|
74
|
+
if %w[show update delete search add remove open close].include?(ARGV[0])
|
75
|
+
params[:id] ||= ARGV[1]
|
63
76
|
end
|
77
|
+
|
78
|
+
# Print the results
|
79
|
+
puts tracker.send(ARGV[0], params)
|
80
|
+
rescue ActiveRecord::StatementInvalid
|
81
|
+
puts "Database error. Use 'trac bootstrap' to create a new database."
|
64
82
|
end
|
65
|
-
|
data/lib/comment.rb
ADDED
data/lib/environment.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "active_record"
|
3
|
+
require "stringio"
|
4
|
+
require "optparse"
|
5
|
+
|
6
|
+
require File.dirname(__FILE__) + "/ticket.rb"
|
7
|
+
require File.dirname(__FILE__) + "/comment.rb"
|
8
|
+
require File.dirname(__FILE__) + "/tracker.rb"
|
9
|
+
|
10
|
+
# Find .tracker_db
|
11
|
+
module Environment
|
12
|
+
# Search for the database unless you're doing a bootstrap
|
13
|
+
if ARGV[0] != "bootstrap" && ENV['TRACKER_ENV'] != "testing"
|
14
|
+
directory = "."
|
15
|
+
i = 0
|
16
|
+
6.times do
|
17
|
+
break if File.exists?(directory + "/.tracker_db")
|
18
|
+
directory += "/.."
|
19
|
+
i += 1
|
20
|
+
end
|
21
|
+
directory = nil if i == 6
|
22
|
+
end
|
23
|
+
|
24
|
+
DIRECTORY = directory || "."
|
25
|
+
DATABASE = ENV['TRACKER_ENV'] == "testing" ? DIRECTORY + "/.tracker_test_db" : DIRECTORY + "/.tracker_db"
|
26
|
+
end
|
27
|
+
|
28
|
+
ActiveRecord::Base.establish_connection(
|
29
|
+
:adapter => "sqlite3",
|
30
|
+
:database => Environment::DATABASE
|
31
|
+
)
|
data/lib/ticket.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
class Ticket < ActiveRecord::Base
|
2
|
+
has_many :comments, :order => "position", :dependent => :delete_all
|
3
|
+
|
4
|
+
OPEN, CLOSED = 0, 1
|
5
|
+
|
6
|
+
def self.search(phrase, options = {})
|
7
|
+
conditions = { :state => (options.delete(:state) || OPEN) }
|
8
|
+
self.with_scope(:find => options.merge(:conditions => conditions) ) do
|
9
|
+
self.find(:all, :conditions => ["keywords LIKE ? OR message LIKE ?", "%" + phrase + "%", "%" + phrase + "%"])
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def summary
|
14
|
+
digits = self.number.to_s.length
|
15
|
+
trim_to = 65 - digits
|
16
|
+
self.message.length > 60 ? self.message[0..trim_to] + "..." : self.message
|
17
|
+
end
|
18
|
+
|
19
|
+
def open
|
20
|
+
self.update_attributes!(:state => OPEN)
|
21
|
+
end
|
22
|
+
|
23
|
+
def close
|
24
|
+
self.update_attributes!(:state => CLOSED)
|
25
|
+
end
|
26
|
+
|
27
|
+
def open?
|
28
|
+
self.state == 0
|
29
|
+
end
|
30
|
+
|
31
|
+
def closed?
|
32
|
+
self.state == 1
|
33
|
+
end
|
34
|
+
|
35
|
+
def number
|
36
|
+
self.id
|
37
|
+
end
|
38
|
+
end
|
data/lib/tracker.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
class Tracker
|
2
|
+
def bootstrap(params = {})
|
3
|
+
$stdout = StringIO.new
|
4
|
+
ActiveRecord::Schema.define do
|
5
|
+
create_table "tickets" do |t|
|
6
|
+
t.column "keywords", :string
|
7
|
+
t.column "message", :text
|
8
|
+
t.column "state", :int, :default => 0
|
9
|
+
t.column "updated_at", :datetime
|
10
|
+
end
|
11
|
+
|
12
|
+
create_table "comments" do |t|
|
13
|
+
t.column "ticket_id", :int
|
14
|
+
t.column "position", :int
|
15
|
+
t.column "message", :text
|
16
|
+
end
|
17
|
+
end
|
18
|
+
result = "New database '#{Environment::DATABASE}' was created"
|
19
|
+
rescue ActiveRecord::StatementInvalid => e
|
20
|
+
result = "Could not create the database '#{Environment::DATABASE}' because it already exists"
|
21
|
+
ensure
|
22
|
+
$stdout = STDOUT
|
23
|
+
return result
|
24
|
+
end
|
25
|
+
|
26
|
+
def create(params = {})
|
27
|
+
ticket = Ticket.create(params)
|
28
|
+
"Created new ticket ##{ticket.number}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def show(params = {})
|
32
|
+
find_ticket(params[:id]) do |ticket|
|
33
|
+
show_ticket(ticket)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def list(params = {})
|
38
|
+
conditions = { :state => Ticket::OPEN }.merge(params)
|
39
|
+
tickets = Ticket.with_scope(:find => { :conditions => conditions }) do
|
40
|
+
Ticket.find(:all, :order => "updated_at DESC")
|
41
|
+
end
|
42
|
+
|
43
|
+
return list_tickets(tickets)
|
44
|
+
end
|
45
|
+
|
46
|
+
def search(params = {})
|
47
|
+
phrase = params.delete(:id)
|
48
|
+
tickets = Ticket.search(phrase, params.merge(:order => "updated_at DESC"))
|
49
|
+
|
50
|
+
return list_tickets(tickets)
|
51
|
+
end
|
52
|
+
|
53
|
+
def update(params = {})
|
54
|
+
find_ticket(params[:id]) do |ticket|
|
55
|
+
params.delete(:id)
|
56
|
+
ticket.update_attributes!(params)
|
57
|
+
"Ticket ##{ticket.number} was updated"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def delete(params = {})
|
62
|
+
find_ticket(params[:id]) do |ticket|
|
63
|
+
ticket.destroy
|
64
|
+
"Ticket ##{ticket.number} was deleted"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def add(params = {})
|
69
|
+
find_ticket(params[:id]) do |ticket|
|
70
|
+
ticket.comments.create(:message => params[:message])
|
71
|
+
"New comment was added to ticket ##{ticket.number}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def remove(params = {})
|
76
|
+
find_ticket(params[:id]) do |ticket|
|
77
|
+
ticket.comments[params[:position] - 1].destroy
|
78
|
+
"Comment ##{params[:position]} was removed from ticket ##{ticket.number}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def open(params = {})
|
83
|
+
find_ticket(params[:id]) do |ticket|
|
84
|
+
ticket.open
|
85
|
+
"Opened ticket ##{ticket.number}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def close(params = {})
|
90
|
+
find_ticket(params[:id]) do |ticket|
|
91
|
+
ticket.close
|
92
|
+
"Closed ticket ##{ticket.number}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
def find_ticket(ticket_id)
|
98
|
+
ticket = Ticket.find_by_id(ticket_id)
|
99
|
+
if ticket
|
100
|
+
yield ticket
|
101
|
+
else
|
102
|
+
"Could not find ticket ##{ticket_id}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def list_tickets(tickets)
|
107
|
+
if tickets == nil || tickets.size == 0
|
108
|
+
"There are currently no tickets in the database"
|
109
|
+
elsif tickets.size == 1
|
110
|
+
show_ticket(tickets[0])
|
111
|
+
else
|
112
|
+
tickets.reverse.map do |ticket|
|
113
|
+
"##{ticket.number}:#{" " * (5 - ticket.number.to_s.length)}#{ticket.summary}"
|
114
|
+
end.join("\n")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def show_ticket(ticket)
|
119
|
+
"Ticket: ##{ticket.number}\n" +
|
120
|
+
(ticket.keywords.blank? ? "" : "Keywords: #{ticket.keywords}\n") +
|
121
|
+
"Messages: - #{ticket.updated_at.to_s(:short)}\n" +
|
122
|
+
" * - #{ticket.message}\n" +
|
123
|
+
ticket.comments.map do |comment|
|
124
|
+
" #{comment.position} - #{comment.message}"
|
125
|
+
end.join("\n")
|
126
|
+
end
|
127
|
+
end
|