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 CHANGED
@@ -1,18 +1,45 @@
1
- == Using tracker (or whatever it's named)
2
- tracker help
3
- Can be used with alternative file, use the -f option.
4
- tracker -f /path/to/sqlite3_file
5
-
6
- == Dependencies
7
- Ruby 1.8
8
- SWIG
9
- SQLite3
10
- SQLite3-Ruby interface
11
-
12
- == To add a new action
13
- * Implement it in Bug class (for whatever bug needs that doesn't have)
14
- * Add the command to the factory in lib/factory.rb
15
- * Make sure the options are valid. You can add new options in the hash in tracker.rb.
16
-
17
- == LICENSE
18
- See MIT-LICENSE.
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
@@ -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
- # Tracker is under the MIT-LICENSE
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
- # Handles connection to the database
4
+ params = {}
6
5
 
7
- require File.dirname(__FILE__) + "/../lib/interface.rb"
8
- require File.dirname(__FILE__) + "/../lib/database.rb"
9
-
10
- # The default database file_name
11
- DEFAULT_DATABASE_FILE_NAME = ".tracker_db"
12
-
13
- # -----------------------------------------------------------------------------
14
- # Begin executing the script here.
15
- # -----------------------------------------------------------------------------
16
-
17
- # What file should we use for the database?
18
- database = DEFAULT_DATABASE_FILE_NAME
19
-
20
- # What to do if no database can be found
21
- def no_database_error(database_file = "the current")
22
- puts "Could not find '.tracker_db' in #{database_file} or any parent directories."
23
- puts "Try '#{File.basename(__FILE__)} create' to create a new database in the current directory or '#{File.basename(__FILE__)} help' to see available commands."
24
- exit
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
- # Is the user creating a new database?
28
- if ARGV.include?("create") && !ARGV.include?("help")
29
- if File.exists?(database)
30
- puts "A '.tracker_db' database already exists in the current directory. Try removing it to create a new one."
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
- else
33
- Database::create_database(database)
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
- # Find the database in parent directories
39
- iteration = 0
40
- while !File.exists?(database) && iteration < 10
41
- iteration += 1
42
- database = File.join("..", database)
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
- # Could we find the database?
46
- if iteration == 10 && !ARGV.include?("help") && !ARGV.include?("-h") && !ARGV.include?("--help") && !ARGV.include?("--file") && !ARGV.include?("-f")
47
- no_database_error
48
- else
49
- if ARGV.include?("-f") || ARGV.include?("--file")
50
- index = ARGV.index("-f") + 1 if ARGV.include?("-f")
51
- index = ARGV.index("--file") + 1 if ARGV.include?("--file")
52
- database = ARGV[index]
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
-
@@ -0,0 +1,7 @@
1
+ class Comment < ActiveRecord::Base
2
+ belongs_to :ticket
3
+ acts_as_list :scope => :ticket
4
+ after_save do |comment|
5
+ comment.ticket ? comment.ticket.save! : nil
6
+ end
7
+ end
@@ -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
+ )
@@ -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
@@ -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