fresnel 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,151 @@
1
+ Fresnel
2
+ --------------
3
+
4
+ A console manager to LighthouseApp.com using the official lighthouse api
5
+
6
+ With Fresnel you can browse you complete lighthouse account.
7
+
8
+ Current features :
9
+ ==================
10
+
11
+ - list projects
12
+ - list ticket bins
13
+ - list tickets (also in bins)
14
+ - create tickets
15
+ - assign tickets
16
+ - comment on tickets
17
+ - state changes
18
+
19
+
20
+ How to install
21
+ ==============
22
+
23
+ Use 'rake gem' to build the gem or 'rake install' to build and install the gem.
24
+
25
+ Cache
26
+ =====
27
+
28
+ You can control the cache time in your global config file : ~/.fresnel
29
+
30
+ add a key like :
31
+ cache_timeout: 60
32
+
33
+ This will set the timeout of the cache to 1 minute.
34
+ When fresnel makes changes, the cache will be invalidated automagically.
35
+ We would recommend to use a minimum of 10 seconds.
36
+
37
+ Terminal size
38
+ =============
39
+
40
+ By default we detect your terminal size,
41
+ if that fails it will be set to 80.
42
+ Though if you like to override it you can !
43
+
44
+ in your global config file : ~/.fresnel
45
+ add a key like :
46
+ term_size: 60
47
+
48
+ The ticket overview table will add columns if it fits, in the following order :
49
+
50
+ * ticket number *
51
+ * state *
52
+ * title *
53
+ - assigned to
54
+ - by
55
+ - tags
56
+ - created at
57
+ - updated at
58
+
59
+ items with a * are always shown ;)
60
+
61
+ Getting started
62
+ ===============
63
+
64
+ Once you have Fresnel installed, run 'fresnel help' to see the syntax.
65
+
66
+ Problems, Comments and Suggestions
67
+ ==================================
68
+
69
+ Please post them on https://govannon.lighthouseapp.com/projects/42260-fresnel/
70
+ or mail it to : ticket+govannon.42260-7vwej7yr@lighthouseapp.com
71
+
72
+
73
+
74
+ Examples
75
+ ========
76
+
77
+ fresnel help
78
+
79
+ +-----------------------------------------------------------------------------+
80
+ | Fresnel - A lighthouseapp console manager - help |
81
+ +-----------------------------------------------------------------------------+
82
+ | |
83
+ | Fresnel is a Console App that helps manage Lighthouse (LH). |
84
+ | You can find LH at http://lighthouseapp.com |
85
+ | |
86
+ | fresnel help This screen |
87
+ | fresnel bins Show all ticket bins |
88
+ | fresnel bin <id> Show ticket in bin <id> |
89
+ | fresnel projects Show all projects |
90
+ | fresnel <id> comment Show comments for ticket |
91
+ | fresnel <id> Show ticket details |
92
+ | fresnel <id> assign Assign ticket to user |
93
+ | fresnel <id> claim Assign ticket to self |
94
+ | fresnel <id> online Open browser for ticket |
95
+ | fresnel <id> [open|closed|hold|resolved|invalid] Change ticket state |
96
+ | fresnel tickets Show all tickets |
97
+ | fresnel create Create a ticket |
98
+ | |
99
+ +-----------------------------------------------------------------------------+
100
+ | Created by Narnach & Smeevil - licence : mit |
101
+ +-----------------------------------------------------------------------------+
102
+
103
+ fresnel projects
104
+
105
+ fetching projects...
106
+ +---+--------------+--------+--------------+
107
+ | # | project name | public | open tickets |
108
+ +---+--------------+--------+--------------+
109
+ | 0 | Fresnel | true | 3 |
110
+ | 1 | Rails | true | 123 |
111
+ +---+--------------+--------+--------------+
112
+
113
+
114
+ fresnel tickets
115
+
116
+ Fetching unresolved tickets...
117
+ +----+-------+--------------------------------------------------------+--------+------------------+-------------+----------------+----------------+
118
+ | # | state | title | tags | by | assigned to | created at | updated at |
119
+ +----+-------+--------------------------------------------------------+--------+------------------+-------------+----------------+----------------+
120
+ | 10 | new | Add option to open Hoptoad link in browser | medium | Wes Oldenbeuving | nobody | Today 11:22 | Today 11:23 |
121
+ | 9 | new | cache invalidation | medium | Smeevil | nobody | Today 08:17 | Today 08:18 |
122
+ | 6 | open | would be nice if we could create a new lighthouse p... | low | Smeevil | Smeevil | 02-12-09 22:10 | 02-12-09 22:16 |
123
+ +----+-------+--------------------------------------------------------+--------+------------------+-------------+----------------+----------------+
124
+
125
+ fresnel 6
126
+
127
+ +-----------------------------------------------------------------------------+
128
+ | Ticket #6 : would be nice if we could create a new lighthouse projec... |
129
+ | Date : 02-12-09 22:10 by Smeevil |
130
+ | Tags : low |
131
+ +-----------------------------------------------------------------------------+
132
+ | |
133
+ | |
134
+ +-----------------------------------------------------------------------------+
135
+
136
+ Assignment changed 02-12-09 21:14 => Smeevil by Smeevil
137
+
138
+ +-----------------------------------------------------------------------------+
139
+ | Smeevil 02-12-09 21:24 |
140
+ +-----------------------------------------------------------------------------+
141
+ | |
142
+ | seems its changing state ! |
143
+ | now lets see if we can do it again ! |
144
+ | |
145
+ +-----------------------------------------------------------------------------+
146
+ | Assignment changed => Wes Oldenbeuving |
147
+ +-----------------------------------------------------------------------------+
148
+
149
+ Assignment changed 02-12-09 22:10 => Smeevil by Smeevil
150
+
151
+ Current state : open
@@ -0,0 +1,54 @@
1
+ require "rake"
2
+ require "rake/clean"
3
+ require "rake/gempackagetask"
4
+ require 'rubygems'
5
+
6
+ ################################################################################
7
+ ### Gem
8
+ ################################################################################
9
+
10
+ begin
11
+ # Parse gemspec using the github safety level.
12
+ file = Dir['*.gemspec'].first
13
+ data = File.read(file)
14
+ spec = nil
15
+ # FIXME: Lowered SAFE from 3 to 2 to work with Ruby 1.9 due to rubygems
16
+ # performing a require internally
17
+ Thread.new { spec = eval("$SAFE = 2\n%s" % data)}.join
18
+
19
+ # Create the gem tasks
20
+ Rake::GemPackageTask.new(spec) do |package|
21
+ package.gem_spec = spec
22
+ end
23
+ rescue Exception => e
24
+ printf "WARNING: Error caught (%s): %s\n%s", e.class.name, e.message, e.backtrace[0...5].map {|l| ' %s' % l}.join("\n")
25
+ end
26
+
27
+ desc 'Package and install the gem for the current version'
28
+ task :install => :gem do
29
+ system "sudo gem install -l pkg/%s-%s.gem" % [spec.name, spec.version]
30
+ end
31
+
32
+ desc 'Show files missing from gemspec'
33
+ task :diff do
34
+ files = %w[
35
+ Rakefile
36
+ *README* *readme*
37
+ *LICENSE*
38
+ *.gemspec deps.rip
39
+ bin/*
40
+ lib/**/*
41
+ spec/**/*
42
+ ].map {|pattern| Dir.glob(pattern)}.flatten.select{|f| File.file?(f)}
43
+ missing_files = files - spec.files
44
+ extra_files = spec.files - files
45
+ puts "Missing files:"
46
+ puts missing_files.join(" ")
47
+ puts "Extra files:"
48
+ puts extra_files.join(" ")
49
+ end
50
+
51
+ desc 'Local install the latest gem version'
52
+ task :reinstall do
53
+ system("rm -f pkg/*.gem && rake gem && gem install pkg/*.gem")
54
+ end
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ require 'fresnel'
3
+ require 'fresnel/cli'
4
+
5
+ COLOR=false
6
+ @@term_size=`tput cols`.to_i
7
+ @@term_size=80 if @@term_size==0 #if tputs does not exist
8
+ @@debug=false
9
+ @@cache_timeout=1.minute
10
+
11
+ Signal.trap("SIGINT") {
12
+ puts "Stopping..."
13
+ exit(0)
14
+ }
15
+
16
+ system("clear")
17
+ Cli.new(ARGV).run!
@@ -0,0 +1,36 @@
1
+ Gem::Specification.new do |s|
2
+ # Project
3
+ s.name = 'fresnel'
4
+ s.summary = "Fresnel is a console manager to LighthouseApp.com using the official lighthouse api."
5
+ s.description = s.summary
6
+ s.version = '0.5.4'
7
+ s.date = '2009-12-04'
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Gerard de Brieder", "Wes Oldenbeuving"]
10
+ s.email = "smeevil@gmail.com"
11
+ s.homepage = "http://www.github.com/smeevil/fresnel"
12
+
13
+ # Files
14
+ root_files = %w[README.markdown Rakefile fresnel.gemspec]
15
+ bin_files = %w[fresnel]
16
+ fresnel_files = %w[cache cli date_parser frame lighthouse setup_wizard]
17
+ lib_files = %w[fresnel] + fresnel_files.map {|f| "fresnel/#{f}"}
18
+ s.bindir = "bin"
19
+ s.require_path = "lib"
20
+ s.executables = bin_files
21
+ s.test_files = []
22
+ s.files = root_files + s.test_files + bin_files.map {|f| 'bin/%s' % f} + lib_files.map {|f| 'lib/%s.rb' % f}
23
+
24
+ # rdoc
25
+ s.has_rdoc = true
26
+ s.extra_rdoc_files = %w[ README.markdown]
27
+ s.rdoc_options << '--inline-source' << '--line-numbers' << '--main' << 'README.rdoc'
28
+
29
+ # Dependencies
30
+ s.add_dependency 'activesupport', ">= 2.3.0"
31
+ s.add_dependency 'terminal-table', ">= 1.3.0"
32
+ s.add_dependency 'highline', ">= 1.5.1"
33
+
34
+ # Requirements
35
+ s.required_ruby_version = ">= 1.8.0"
36
+ end
@@ -0,0 +1,479 @@
1
+ require 'active_support'
2
+ require 'terminal-table/import'
3
+ require 'highline/import'
4
+
5
+ require "fresnel/lighthouse"
6
+ require "fresnel/date_parser"
7
+ require "fresnel/cache"
8
+ require "fresnel/setup_wizard"
9
+ require "fresnel/frame"
10
+
11
+ HighLine.track_eof = false
12
+
13
+ class String
14
+ def wrap(col = 80)
15
+ self.gsub(/(.{1,#{col}})( +|$\n?)|(.{1,#{col}})/,
16
+ "\\1\\3\n")
17
+ end
18
+
19
+ def truncate(size)
20
+ "#{self.strip[0..size]}#{"..." if self.size>size}"
21
+ end
22
+ end
23
+
24
+ class Fresnel
25
+ attr_reader :global_config_file, :project_config_file, :app_description
26
+ attr_accessor :lighthouse, :current_project_id, :cache, :cache_timeout, :current_user_id
27
+
28
+ def initialize(options=Hash.new)
29
+ @global_config_file="#{ENV['HOME']}/.fresnel"
30
+ @project_config_file=File.expand_path('.fresnel')
31
+ @app_description="A lighthouseapp console manager"
32
+ @lighthouse=Lighthouse
33
+ @cache=Cache.new
34
+ Lighthouse.account, Lighthouse.token, @current_user_id = load_global_config
35
+ @current_project_id=load_project_config
36
+
37
+ end
38
+
39
+ def load_global_config
40
+ if File.exists? self.global_config_file
41
+ config = YAML.load_file(self.global_config_file)
42
+
43
+ @@cache_timeout=config['cache_timeout'] if config.has_key?('cache_timeout')
44
+ @@debug=config['debug'] if config.has_key?('debug')
45
+ @@term_size=config['term_size'] if config.has_key?('term_size')
46
+
47
+ if config && config.class==Hash && config.has_key?('account') && config.has_key?('token') && config.has_key?('user_id')
48
+ return [config['account'], config['token'], config['user_id']]
49
+ else
50
+ puts Frame.new(:header=>"Warning !",:body=>"global config did not validate , recreating")
51
+ SetupWizard.global(self)
52
+ load_global_config
53
+ end
54
+ else
55
+ puts Frame.new(:header=>"Notice",:body=>"global config not found at #{self.global_config_file}, starting wizard")
56
+ SetupWizard.global(self)
57
+ load_global_config
58
+ end
59
+ end
60
+
61
+ def load_project_config
62
+ if File.exists? self.project_config_file
63
+ config = YAML.load_file(self.project_config_file)
64
+ if config && config.class==Hash && config.has_key?('project_id')
65
+ return config['project_id']
66
+ else
67
+ puts Frame.new(:header=>"Warning !",:body=>"project config found but project_id was not declared")
68
+ load_project_config
69
+ end
70
+ else
71
+ puts Frame.new(:header=>"Notice",:body=>"project config not found at #{self.project_config_file}, starting wizard")
72
+ SetupWizard.project(self)
73
+ load_project_config
74
+ end
75
+ end
76
+
77
+ def account
78
+ lighthouse.account
79
+ end
80
+
81
+ def token
82
+ lighthouse.token
83
+ end
84
+
85
+ def ask_for_action(actions_available="")
86
+ if actions_available.present?
87
+ puts actions_available.wrap
88
+ regexp="^(#{actions_available.scan(/\[(.*?)\]/).flatten.join("|")}|[0-9]+)$"
89
+ else
90
+ regexp="^(q|[0-9]+)$"
91
+ end
92
+ ask("Action : ") do |q|
93
+ q.default="q"
94
+ q.validate=/#{regexp}/
95
+ end
96
+ end
97
+
98
+ def create_project
99
+ puts "create project is not implemented yet"
100
+ end
101
+
102
+ def projects(options=Hash.new)
103
+ options[:object]||=false
104
+ system("clear") unless options[:clear]==false || options[:object]
105
+ options[:selectable]||false
106
+ print "Fetching projects..." unless options[:object]
107
+ projects_data=cache.load(:name=>"fresnel_projects",:action=>"Lighthouse::Project.find(:all)")
108
+ puts " [done] - data is #{projects_data.age}s old , max is #{@@cache_timeout}s"
109
+ project_table = table do |t|
110
+ t.headings=[]
111
+ t.headings << '#' if options[:selectable]
112
+ t.headings += ['project name', 'public', 'open tickets']
113
+
114
+ projects_data.each_with_index do |project,i|
115
+ row=Array.new
116
+ row << i if options[:selectable]
117
+ row+=[project.name, project.public, {:value=>project.open_tickets_count, :alignment=>:right}]
118
+ t << row
119
+ end
120
+ end
121
+ if options[:object]
122
+ return projects_data
123
+ else
124
+ puts(project_table)
125
+ unless options[:setup]
126
+ action=ask_for_action("[q]uit, [c]reate or project #")
127
+ case action
128
+ when "c" then create_project
129
+ when /\d+/ then tickets(:project_id=>projects_data[action.to_i].id)
130
+ else
131
+ exit(0)
132
+ end
133
+ end
134
+ end
135
+ end
136
+
137
+ def tickets(options=Hash.new)
138
+ system("clear")
139
+ options[:all] ? print("Fetching all tickets#{" in bin #{options[:bin_name]}" if options[:bin_name].present?}...") : print("Fetching unresolved tickets#{" in bin #{options[:bin_name]}" if options[:bin_name].present?}...")
140
+ STDOUT.flush
141
+ project_id=options[:project_id]||self.current_project_id
142
+ tickets=options[:tickets]||cache.load(:name=>"fresnel_project_#{project_id}_tickets#{"_all" if options[:all]}", :action=>"Lighthouse::Ticket.find(:all, :params=>{:project_id=>#{project_id} #{",:q=>'not-state:closed'" unless options[:all]}})")
143
+ puts " [done] - data is #{tickets.age}s old , max is #{@@cache_timeout}s"
144
+ if tickets.any?
145
+ tickets_table = table do |t|
146
+ prepped_headings=[
147
+ {:value=>'#',:alignment=>:center},
148
+ {:value=>'state',:alignment=>:center},
149
+ {:value=>'title',:alignment=>:center},
150
+ ]
151
+ prepped_headings << {:value=>'assigned to',:alignment=>:center} if @@term_size>=90
152
+ prepped_headings << {:value=>'by',:alignment=>:center} if @@term_size>=105
153
+ prepped_headings << {:value=>'tags',:alignment=>:center} if @@term_size>=120
154
+ prepped_headings << 'created at' if @@term_size>=140
155
+ prepped_headings << 'updated at' if @@term_size>=160
156
+
157
+ t.headings = prepped_headings #must assign the heading at once, else it will b0rk the output
158
+
159
+ tickets.sort_by(&:number).reverse.each do |ticket|
160
+ cols=[
161
+ {:value=>ticket.number, :alignment=>:right},
162
+ {:value=>ticket.state,:alignment=>:center},
163
+ "#{ticket.title.truncate(50)}"
164
+ ]
165
+ cols << (ticket.assigned_user_name.split(" ").first.truncate(10) rescue "nobody") if @@term_size>=90
166
+ cols << ticket.creator_name.split(" ").first.truncate(10) if @@term_size>=105
167
+ cols << (ticket.tag.truncate(9) rescue "") if @@term_size>=120
168
+ cols << {:value=>DateParser.string(ticket.created_at.to_s), :alignment=>:right} if @@term_size>=140
169
+ cols << {:value=>DateParser.string(ticket.updated_at.to_s), :alignment=>:right} if @@term_size>=160
170
+
171
+ t << cols #must assign the cols at once, else it will b0rk the output
172
+ end
173
+ end
174
+ puts tickets_table
175
+ action=ask_for_action("[q]uit, [b]ins, [p]rojects, #{options[:all] ? "[u]nresolved" : "[a]ll"}, [c]reate or ticket #")
176
+ case action
177
+ when "b" then get_bins
178
+ when "c" then create
179
+ when "p" then projects(:selectable=>true)
180
+ when "a" then tickets(:all=>true)
181
+ when "u" then self.tickets
182
+ when /\d+/ then show_ticket(action)
183
+ else
184
+ exit(0)
185
+ end
186
+ else
187
+ puts Frame.new(:header=>"Notice",:body=>"no #{"unresolved " unless options[:all]}tickets #{"in bin #{options[:bin_name]}"}...")
188
+ action=ask_for_action("[q]uit, [b]ins, [p]rojects, [u]nresolved, [a]ll, [c]reate")
189
+ case action
190
+ when "b" then get_bins
191
+ when "c" then create
192
+ when "p" then projects(:selectable=>true)
193
+ when "a" then tickets(:all=>true)
194
+ when "u" then self.tickets
195
+ else
196
+ exit(0)
197
+ end
198
+
199
+ end
200
+
201
+ end
202
+
203
+ def get_bins(project_id=self.current_project_id)
204
+ system("clear")
205
+ print "Fetching ticket bins..."
206
+ STDOUT.flush
207
+ bins=cache.load(:name=>"fresnel_project_#{project_id}_bins",:action=>"Lighthouse::Project.find(#{project_id}).bins")
208
+ puts " [done] - data is #{bins.age}s old , max is #{@@cache_timeout}s"
209
+ bins.reject!{|b|true unless b.user_id==self.current_user_id || b.shared}
210
+ bins_table = table do |t|
211
+ t.headings = ['#', 'bin', 'tickets', 'query']
212
+ bins.each_with_index do |bin,i|
213
+ t << [i, bin.name,{:value=>bin.tickets_count, :alignment=>:right},bin.query]
214
+ end
215
+ end
216
+ puts bins_table
217
+ bin_id=ask_for_action
218
+ if bin_id=="q"
219
+ exit(0)
220
+ else
221
+ puts "Fetching tickets in bin : #{bins[bin_id.to_i].name}"
222
+ tickets(:tickets=>bins[bin_id.to_i].tickets)
223
+ def tickets.age=(seconds)
224
+ @age_in_seconds=seconds
225
+ end
226
+ def tickets.age
227
+ @age_in_seconds
228
+ end
229
+ tickets.age=0
230
+ return tickets
231
+ end
232
+ end
233
+
234
+ def get_tickets_in_bin(bin)
235
+ bins=cache.load(:name=>"fresnel_project_#{self.current_project_id}_bins",:action=>"Lighthouse::Project.find(#{self.current_project_id}).bins")
236
+ bins.reject!{|b|true unless b.user_id==self.current_user_id || b.shared}
237
+ data=bins[bin.to_i].tickets
238
+
239
+ def data.age=(seconds)
240
+ @age_in_seconds=seconds
241
+ end
242
+ def data.age
243
+ @age_in_seconds
244
+ end
245
+ data.age=0
246
+
247
+ tickets(:tickets=>data, :bin_name=>bins[bin.to_i].name)
248
+ end
249
+
250
+ def get_ticket(number)
251
+ cache.load(:name=>"fresnel_ticket_#{number}",:action=>"Lighthouse::Ticket.find(#{number}, :params => { :project_id => #{self.current_project_id} })")
252
+ end
253
+
254
+ def get_project(project_id=self.current_project_id)
255
+ cache.load(:name=>"fresnel_project_#{project_id}",:action=>"Lighthouse::Project.find(#{project_id})")
256
+ end
257
+
258
+ def get_project_members(project_id=self.current_project_id)
259
+ cache.load(:name=>"fresnel_project_#{project_id}_members", :action=>"Lighthouse::Project.find(#{project_id}).memberships", :timeout=>1.day)
260
+ end
261
+
262
+ def show_ticket(number)
263
+ system("clear")
264
+ ticket = get_ticket(number)
265
+ puts Frame.new(
266
+ :header=>[
267
+ "Ticket ##{number} : #{ticket.title.chomp.truncate(55)}",
268
+ "Date : #{DateParser.string(ticket.created_at.to_s)} by #{ticket.creator_name}",
269
+ "Tags : #{ticket.tag}"
270
+ ],
271
+ :body=>ticket.versions.first.body
272
+ )
273
+ ticket.versions.each_with_index do |v,i|
274
+ next if i==0
275
+ if v.respond_to?(:diffable_attributes) && v.body.nil?
276
+ puts "\nState changed #{DateParser.string(v.created_at.to_s)} from #{v.diffable_attributes.state} => #{v.state} by #{v.user_name}\n\n" if v.diffable_attributes.respond_to?(:state)
277
+ puts "\nAssignment changed #{DateParser.string(v.created_at.to_s)} => #{v.assigned_user_name rescue "Nobody"} by #{v.user_name}\n\n" if v.diffable_attributes.respond_to?(:assigned_user)
278
+ else
279
+ user_date=v.user_name.capitalize
280
+ date=DateParser.string(v.created_at.to_s)
281
+ user_date=user_date.ljust((@@term_size-5)-date.size)
282
+ user_date+=date
283
+
284
+ footer=Array.new
285
+ footer<<"State changed from #{v.diffable_attributes.state} => #{v.state}" if v.diffable_attributes.respond_to?(:state)
286
+ footer<<"Assignment changed => #{v.assigned_user_name}" if v.diffable_attributes.respond_to?(:assigned_user)
287
+
288
+ puts Frame.new(:header=>user_date,:body=>v.body,:footer=>footer)
289
+ end
290
+ end
291
+ puts "Current state : #{ticket.versions.last.state}"
292
+ action=ask_for_action("[q]uit, [t]ickets, [b]ins, [c]omment, [a]ssign, [r]esolve, [s]elf, [o]pen, [h]old, [w]eb, [l]inks")
293
+ case action
294
+ when "t" then tickets
295
+ when "b" then get_bins
296
+ when "c" then comment(number)
297
+ when "a" then assign(:ticket=>number)
298
+ when "r" then change_state(:ticket=>number,:state=>"resolved")
299
+ when "s" then claim(:ticket=>number)
300
+ when "o" then change_state(:ticket=>number,:state=>"open")
301
+ when "h" then change_state(:ticket=>number,:state=>"hold")
302
+ when "w" then open_browser_for_ticket(number)
303
+ when "l" then links(number)
304
+ else
305
+ exit(0)
306
+ end
307
+ end
308
+
309
+ def links(number)
310
+ ticket = get_ticket(number)
311
+ links = ticket.versions.map{ |version|
312
+ version.body.to_s.scan(/(http|https)(:\/\/)([a-zA-Z0-9.\/_-]+)| (www\.[a-zA-Z0-9.\/_-]+)/).map{ |url|
313
+ url.join
314
+ }
315
+ }.flatten.uniq
316
+ if links.size == 0
317
+ puts "No links found"
318
+ sleep 1
319
+ elsif links.size == 1
320
+ url = links.first
321
+ url="http://#{url}" unless url=~/^http/
322
+ `open '#{url}'`
323
+ else
324
+ link_table=table do |t|
325
+ t.headings=['#','link']
326
+ links.each_with_index{|link,i|t << [i,link]}
327
+ end
328
+ puts link_table
329
+ pick=ask("open link # : ", Integer) do |q|
330
+ q.below=links.size
331
+ q.above=-1
332
+ end
333
+ url=links[pick]
334
+ url="http://#{url}" unless url=~/^http/
335
+ `open '#{url}'`
336
+ end
337
+ show_ticket(number)
338
+ end
339
+
340
+ def comment(number,state=nil)
341
+ puts "create comment for #{number}"
342
+ ticket=get_ticket(number)
343
+ File.open("/tmp/fresnel_ticket_#{number}_comment", "w+") do |f|
344
+ f.puts "# Please enter the comment for this ticket. Lines starting"
345
+ f.puts "# with '#' will be ignored, and an empty message aborts the commit."
346
+ `echo "q" | fresnel #{number}`.each{ |l| f.write "# #{l}" }
347
+ end
348
+ system("mate -w /tmp/fresnel_ticket_#{number}_comment")
349
+ body=Array.new
350
+ File.read("/tmp/fresnel_ticket_#{number}_comment").each do |l|
351
+ body << l unless l=~/^#/
352
+ end
353
+
354
+ body=body.to_s
355
+ if body.blank?
356
+ puts Frame.new(:header=>"Warning !", :body=>"Aborting comment because it was blank !")
357
+ else
358
+ ticket.body=body
359
+ ticket.state=state unless state.nil?
360
+ if ticket.save
361
+ cache.clear(:name=>"fresnel_ticket_#{number}")
362
+ show_ticket(number)
363
+ else
364
+ puts "something went wrong"
365
+ puts $!
366
+ end
367
+ end
368
+ end
369
+
370
+ def create
371
+ system("mate -w /tmp/fresnel_new_ticket")
372
+ if File.exists?("/tmp/fresnel_new_ticket")
373
+ data=File.read("/tmp/fresnel_new_ticket")
374
+ body=Array.new
375
+ title=""
376
+ if data.blank?
377
+ puts Frame.new(:header=>"Warning !", :body=>"Aborting creation because the ticket was blank !")
378
+ else
379
+ data.each do |l|
380
+ if title.blank?
381
+ title=l
382
+ next
383
+ end
384
+ body << l
385
+ end
386
+ body=body.to_s
387
+ tags=ask("Tags : ")
388
+ tags=tags.split(" ")
389
+ end
390
+ puts "creating ticket..."
391
+ ticket = Lighthouse::Ticket.new(
392
+ :project_id=>self.current_project_id,
393
+ :title=>title,
394
+ :body=>body
395
+ )
396
+ ticket.tags=tags
397
+ if ticket.save
398
+ File.delete("/tmp/fresnel_new_ticket")
399
+ show_ticket(ticket.number)
400
+ else
401
+ puts "something went wrong !"
402
+ puts $!
403
+ end
404
+ else
405
+ puts Frame.new(:header=>"Warning !", :body=>"Aborting creation because the ticket was blank !")
406
+ end
407
+ end
408
+
409
+ def change_state(options)
410
+ puts "should change state to #{options.inspect}"
411
+ ticket=get_ticket(options[:ticket])
412
+ old_state=ticket.state
413
+ options[:state]="resolved" if options[:state]=~/closed?/
414
+ if options[:state]=~/resolved|invalid/
415
+ comment(options[:ticket],options[:state])
416
+ else
417
+ ticket.state=options[:state]
418
+ ticket.assigned_user_id=options[:user_id] if options[:user_id].present?
419
+ if ticket.save
420
+ cache.clear(:name=>"fresnel_ticket_#{options[:ticket]}")
421
+ if options[:user_id].present?
422
+ user_name = Lighthouse::User.find(options[:user_id]).name
423
+ puts Frame.new(:header=>"Success",:body=>"State has changed from #{old_state} to #{options[:state]} #{"and is reassigned to #{user_name}"}")
424
+ end
425
+ show_ticket(options[:ticket])
426
+ else
427
+ puts Frame.new(:header=>"Error !",:body=>"Something went wrong ! #{$!}")
428
+ end
429
+ end
430
+ end
431
+
432
+ def open_browser_for_ticket(number)
433
+ #fast
434
+ #`open "https://#{self.account}.lighthouseapp.com/projects/#{self.current_project_id}/tickets/#{number}"`
435
+ #or save
436
+ puts "opening ticket #{number}in browser"
437
+ `open "#{get_ticket(number).url}"`
438
+ show_ticket(number)
439
+ end
440
+
441
+ def assign(options)
442
+ unless options[:user_id]
443
+ puts "should assign ticket #{options[:ticket]} to someone :"
444
+ members=get_project_members
445
+ members_table = table do |t|
446
+ t.headings = ['#', 'user_id', 'username']
447
+ members.each_with_index do |member,i|
448
+ t << [i, member.user.id, member.user.name]
449
+ end
450
+ end
451
+ puts members_table
452
+ pick=ask("Assign to # : ",Integer) do |q|
453
+ q.above=-1
454
+ q.below=members.count
455
+ end
456
+ options[:user_id]=members[pick].user.id
457
+ end
458
+ ticket=get_ticket(options[:ticket])
459
+ ticket.assigned_user_id=options[:user_id]
460
+ if ticket.save
461
+ cache.clear(:name=>"fresnel_ticket_#{options[:ticket]}")
462
+ user_name = Lighthouse::User.find(options[:user_id]).name
463
+ puts Frame.new(:header=>"Success",:body=>"Reassigned ticket ##{options[:ticket]} to #{user_name} !")
464
+ else
465
+ puts Frame.new(:header=>"Error",:body=>"assigning failed !")
466
+ end
467
+ end
468
+
469
+ def claim(options)
470
+ puts "current user is : #{Lighthouse::User.find(self.current_user_id).name}"
471
+ ticket=get_ticket(options[:ticket])
472
+ if ticket.state=="new"
473
+ change_state(:ticket=>options[:ticket], :state=>"open", :user_id=>self.current_user_id) #get around the cache ...
474
+ else
475
+ assign(:ticket=>options[:ticket],:user_id=>self.current_user_id)
476
+ end
477
+
478
+ end
479
+ end