TicGit-ng 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.
@@ -0,0 +1,77 @@
1
+ module TicGitNG
2
+ module Command
3
+ COMMANDS = {}
4
+ DOC = {}
5
+
6
+ def self.register(mod_name, doc, *commands)
7
+ autoload(mod_name, "ticgit-ng/command/#{mod_name.downcase}")
8
+ DOC[commands] = doc
9
+ commands.each{|cmd| COMMANDS[cmd] = mod_name }
10
+ end
11
+
12
+ register 'Assign', 'Assings a ticket to someone', 'assign'
13
+ register 'Attach', 'Attach file to ticket', 'attach'
14
+ register 'Checkout', 'Checkout a ticket', 'checkout', 'co'
15
+ register 'Comment', 'Comment on a ticket', 'comment'
16
+ register 'List', 'List tickets', 'list'
17
+ register 'Milestone', 'List and modify milestones', 'milestone'
18
+ register 'New', 'Create a new ticket', 'new'
19
+ register 'Points', 'Assign points to a ticket', 'points'
20
+ register 'Recent', 'List recent activities', 'recent'
21
+ register 'Show', 'Show a ticket', 'show'
22
+ register 'State', 'Change state of a ticket', 'state'
23
+ register 'Tag', 'Modify tags of a ticket', 'tag'
24
+ register 'Sync', 'Sync tickets', 'sync'
25
+
26
+ def self.get(command)
27
+ if mod_name = COMMANDS[command]
28
+ const_get(mod_name)
29
+ end
30
+ end
31
+
32
+ def self.usage(action, args)
33
+ option_parser = parser(action, &method(:default_usage))
34
+ option_parser.parse!(args)
35
+ option_parser
36
+ end
37
+
38
+ def self.default_usage(o)
39
+ o.banner = "Usage: ti COMMAND [FLAGS] [ARGS]"
40
+ o.top.append ' ', nil, nil
41
+ o.top.append 'The available ticgit commands are:', nil, nil
42
+
43
+ DOC.each do |commands, doc|
44
+ # get the longest version
45
+ command = commands.sort_by{|cmd| cmd.size }.last
46
+ o.top.append(" %-32s %s" % [command, doc], nil, nil)
47
+ end
48
+ end
49
+
50
+ def self.parser(action, &block)
51
+ OptionParser.new do |o|
52
+ o.banner = "Usage: ti #{action} [FLAGS] [ARGS]"
53
+
54
+ o.base.append ' ', nil, nil
55
+ o.base.append 'Common options:', nil, nil
56
+ o.on_tail('-v', '--version', 'Show the version number'){
57
+ puts TicGitNG::VERSION
58
+ exit
59
+ }
60
+ o.on_tail('-h', '--help', 'Display this help'){
61
+ puts o
62
+ exit
63
+ }
64
+
65
+ if block_given?
66
+ yield(o) if block_given?
67
+ unless o.top.list.empty?
68
+ if action
69
+ o.top.prepend "Options for #{action} command:", nil, nil
70
+ o.top.prepend ' ', nil, nil
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,17 @@
1
+ module TicGitNG
2
+ class Comment
3
+
4
+ attr_reader :base, :user, :added, :comment
5
+
6
+ def initialize(base, file_name, sha)
7
+ @base = base
8
+ @comment = base.git.gblob(sha).contents rescue nil
9
+
10
+ type, date, user = file_name.split('_')
11
+
12
+ @added = Time.at(date.to_i)
13
+ @user = user
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,233 @@
1
+ module TicGitNG
2
+ class Ticket
3
+
4
+ attr_reader :base, :opts
5
+ attr_accessor :ticket_id, :ticket_name
6
+ attr_accessor :title, :state, :milestone, :assigned, :opened, :points
7
+ attr_accessor :comments, :tags, :attachments # arrays
8
+
9
+ def initialize(base, options = {})
10
+ options[:user_name] ||= base.git.config('user.name')
11
+ options[:user_email] ||= base.git.config('user.email')
12
+
13
+ @base = base
14
+ @opts = options || {}
15
+
16
+ @state = 'open' # by default
17
+ @comments = []
18
+ @tags = []
19
+ @attachments = []
20
+ end
21
+
22
+ def self.create(base, title, options = {})
23
+ t = Ticket.new(base, options)
24
+ t.title = title
25
+ t.ticket_name = self.create_ticket_name(title)
26
+ t.save_new
27
+ t
28
+ end
29
+
30
+ def self.open(base, ticket_name, ticket_hash, options = {})
31
+ tid = nil
32
+
33
+ t = Ticket.new(base, options)
34
+ t.ticket_name = ticket_name
35
+
36
+ title, date = self.parse_ticket_name(ticket_name)
37
+ t.opened = date
38
+
39
+ ticket_hash['files'].each do |fname, value|
40
+ if fname == 'TICKET_ID'
41
+ tid = value
42
+ elsif fname == 'TICKET_TITLE'
43
+ t.title = base.git.gblob(value).contents
44
+ else
45
+ # matching
46
+ data = fname.split('_')
47
+
48
+ case data[0]
49
+ when 'ASSIGNED'
50
+ t.assigned = data[1]
51
+ when 'ATTACHMENT'
52
+ t.attachments << TicGitNG::Attachment.new(base, fname, value)
53
+ when 'COMMENT'
54
+ t.comments << TicGitNG::Comment.new(base, fname, value)
55
+ when 'POINTS'
56
+ t.points = base.git.gblob(value).contents.to_i
57
+ when 'STATE'
58
+ t.state = data[1]
59
+ when 'TAG'
60
+ t.tags << data[1]
61
+ when 'TITLE'
62
+ t.title = base.git.gblob(value).contents
63
+ end
64
+ end
65
+ end
66
+
67
+ t.ticket_id = tid
68
+ t
69
+ end
70
+
71
+
72
+ def self.parse_ticket_name(name)
73
+ epoch, title, rand = name.split('_')
74
+ title = title.gsub('-', ' ')
75
+ return [title, Time.at(epoch.to_i)]
76
+ end
77
+
78
+ # write this ticket to the git database
79
+ def save_new
80
+ base.in_branch do |wd|
81
+ base.logger.info "saving #{ticket_name}"
82
+
83
+ Dir.mkdir(ticket_name)
84
+ Dir.chdir(ticket_name) do
85
+ base.new_file('TICKET_ID', ticket_name)
86
+ base.new_file('TICKET_TITLE', title)
87
+ base.new_file('ASSIGNED_' + email, email)
88
+ base.new_file('STATE_' + state, state)
89
+ base.new_file('TITLE', title)
90
+
91
+ # add initial comment
92
+ #COMMENT_080315060503045__schacon_at_gmail
93
+ base.new_file(comment_name(email), opts[:comment]) if opts[:comment]
94
+
95
+ # add initial tags
96
+ if opts[:tags] && opts[:tags].size > 0
97
+ opts[:tags] = opts[:tags].map { |t| t.strip }.compact
98
+ opts[:tags].each do |tag|
99
+ if tag.size > 0
100
+ tag_filename = 'TAG_' + Ticket.clean_string(tag)
101
+ if !File.exists?(tag_filename)
102
+ base.new_file(tag_filename, tag_filename)
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ base.git.add
109
+ base.git.commit("added ticket #{ticket_name}")
110
+ end
111
+ # ticket_id
112
+ end
113
+
114
+ def self.clean_string(string)
115
+ string.downcase.gsub(/[^a-z0-9]+/i, '-')
116
+ end
117
+
118
+ def add_comment(comment)
119
+ return false if !comment
120
+ base.in_branch do |wd|
121
+ Dir.chdir(ticket_name) do
122
+ base.new_file(comment_name(email), comment)
123
+ end
124
+ base.git.add
125
+ base.git.commit("added comment to ticket #{ticket_name}")
126
+ end
127
+ end
128
+
129
+ def change_state(new_state)
130
+ return false if !new_state
131
+ return false if new_state == state
132
+
133
+ base.in_branch do |wd|
134
+ Dir.chdir(ticket_name) do
135
+ base.new_file('STATE_' + new_state, new_state)
136
+ end
137
+ base.git.remove(File.join(ticket_name,'STATE_' + state))
138
+ base.git.add
139
+ base.git.commit("added state (#{new_state}) to ticket #{ticket_name}")
140
+ end
141
+ end
142
+
143
+ def change_assigned(new_assigned)
144
+ new_assigned ||= email
145
+ old_assigned= assigned || ''
146
+ return false if new_assigned == old_assigned
147
+
148
+ base.in_branch do |wd|
149
+ Dir.chdir(ticket_name) do
150
+ base.new_file('ASSIGNED_' + new_assigned, new_assigned)
151
+ end
152
+ base.git.remove(File.join(ticket_name,'ASSIGNED_' + old_assigned))
153
+ base.git.add
154
+ base.git.commit("assigned #{new_assigned} to ticket #{ticket_name}")
155
+ end
156
+ end
157
+
158
+ def change_points(new_points)
159
+ return false if new_points == points
160
+
161
+ base.in_branch do |wd|
162
+ Dir.chdir(ticket_name) do
163
+ base.new_file('POINTS', new_points)
164
+ end
165
+ base.git.add
166
+ base.git.commit("set points to #{new_points} for ticket #{ticket_name}")
167
+ end
168
+ end
169
+
170
+ def add_tag(tag)
171
+ return false if !tag
172
+ added = false
173
+ tags = tag.split(',').map { |t| t.strip }
174
+ base.in_branch do |wd|
175
+ Dir.chdir(ticket_name) do
176
+ tags.each do |add_tag|
177
+ if add_tag.size > 0
178
+ tag_filename = 'TAG_' + Ticket.clean_string(add_tag)
179
+ if !File.exists?(tag_filename)
180
+ base.new_file(tag_filename, tag_filename)
181
+ added = true
182
+ end
183
+ end
184
+ end
185
+ end
186
+ if added
187
+ base.git.add
188
+ base.git.commit("added tags (#{tag}) to ticket #{ticket_name}")
189
+ end
190
+ end
191
+ end
192
+
193
+ def remove_tag(tag)
194
+ return false if !tag
195
+ removed = false
196
+ tags = tag.split(',').map { |t| t.strip }
197
+ base.in_branch do |wd|
198
+ tags.each do |add_tag|
199
+ tag_filename = File.join(ticket_name, 'TAG_' + Ticket.clean_string(add_tag))
200
+ if File.exists?(tag_filename)
201
+ base.git.remove(tag_filename)
202
+ removed = true
203
+ end
204
+ end
205
+ if removed
206
+ base.git.commit("removed tags (#{tag}) from ticket #{ticket_name}")
207
+ end
208
+ end
209
+ end
210
+
211
+ def path
212
+ File.join(state, ticket_name)
213
+ end
214
+
215
+ def comment_name(email)
216
+ 'COMMENT_' + Time.now.to_i.to_s + '_' + email
217
+ end
218
+
219
+ def email
220
+ opts[:user_email] || 'anon'
221
+ end
222
+
223
+ def assigned_name
224
+ assigned.split('@').first rescue ''
225
+ end
226
+
227
+ def self.create_ticket_name(title)
228
+ [Time.now.to_i.to_s, Ticket.clean_string(title), rand(999).to_i.to_s].join('_')
229
+ end
230
+
231
+
232
+ end
233
+ end
@@ -0,0 +1,3 @@
1
+ module TicGitNG
2
+ VERSION = '1.0.0'
3
+ end
data/lib/ticgit-ng.rb ADDED
@@ -0,0 +1,35 @@
1
+ require 'fileutils'
2
+ require 'logger'
3
+ require 'optparse'
4
+ require 'ostruct'
5
+ require 'set'
6
+ require 'yaml'
7
+
8
+ # Add the directory containing this file to the start of the load path if it
9
+ # isn't there already.
10
+ $:.unshift(File.dirname(__FILE__)) unless
11
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
12
+
13
+ require 'rubygems'
14
+ # requires git >= 1.0.5
15
+ require 'git'
16
+ require 'ticgit-ng/base'
17
+ require 'ticgit-ng/cli'
18
+
19
+ module TicGitNG
20
+ autoload :VERSION, 'ticgit-ng/version'
21
+ autoload :Comment, 'ticgit-ng/comment'
22
+ autoload :Ticket, 'ticgit-ng/ticket'
23
+
24
+ # options
25
+ # :logger => Logger.new(STDOUT)
26
+ def self.open(git_dir, options = {})
27
+ Base.new(git_dir, options)
28
+ end
29
+
30
+ class OpenStruct < ::OpenStruct
31
+ def to_hash
32
+ @table.dup
33
+ end
34
+ end
35
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: TicGit-ng
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Scott Chacon
14
+ - Jeff Welling
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2011-02-15 00:00:00 -05:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: git
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 3
31
+ segments:
32
+ - 0
33
+ version: "0"
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 3
45
+ segments:
46
+ - 0
47
+ version: "0"
48
+ type: :development
49
+ version_requirements: *id002
50
+ description: TicGit-ng is a simple ticketing system, roughly similar to the Lighthouse model, that is based in git.
51
+ email:
52
+ - Jeff.Welling@gmail.com
53
+ executables:
54
+ - ti
55
+ - ticgitweb
56
+ extensions: []
57
+
58
+ extra_rdoc_files: []
59
+
60
+ files:
61
+ - bin/ti
62
+ - bin/ticgitweb
63
+ - lib/ticgit-ng.rb
64
+ - lib/ticgit-ng/base.rb
65
+ - lib/ticgit-ng/ticket.rb
66
+ - lib/ticgit-ng/cli.rb
67
+ - lib/ticgit-ng/version.rb
68
+ - lib/ticgit-ng/command/assign.rb
69
+ - lib/ticgit-ng/command/state.rb
70
+ - lib/ticgit-ng/command/new.rb
71
+ - lib/ticgit-ng/command/recent.rb
72
+ - lib/ticgit-ng/command/sync.rb
73
+ - lib/ticgit-ng/command/show.rb
74
+ - lib/ticgit-ng/command/points.rb
75
+ - lib/ticgit-ng/command/milestone.rb
76
+ - lib/ticgit-ng/command/tag.rb
77
+ - lib/ticgit-ng/command/comment.rb
78
+ - lib/ticgit-ng/command/list.rb
79
+ - lib/ticgit-ng/command/checkout.rb
80
+ - lib/ticgit-ng/command.rb
81
+ - lib/ticgit-ng/comment.rb
82
+ - LICENSE_MIT
83
+ - LICENSE_GPL
84
+ - README.mkd
85
+ has_rdoc: true
86
+ homepage: https://github.com/jeffWelling/ticgit
87
+ licenses: []
88
+
89
+ post_install_message:
90
+ rdoc_options: []
91
+
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ hash: 3
100
+ segments:
101
+ - 0
102
+ version: "0"
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ hash: 3
109
+ segments:
110
+ - 0
111
+ version: "0"
112
+ requirements: []
113
+
114
+ rubyforge_project: ticgit-ng
115
+ rubygems_version: 1.3.7
116
+ signing_key:
117
+ specification_version: 3
118
+ summary: Git based distributed ticketing system
119
+ test_files: []
120
+