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