require 'ticgit'
require 'optparse'
require 'yaml'
# used Cap as a model for this - thanks Jamis
module TicGit
class CLI
# The array of (unparsed) command-line options
attr_reader :action, :options, :args, :tic
def self.execute
parse(ARGV).execute!
end
def self.parse(args)
cli = new(args)
cli.parse_options!
cli
end
def initialize(args)
@args = args.dup
@tic = TicGit.open('.', :keep_state => true)
$stdout.sync = true # so that Net::SSH prompts show up
rescue NoRepoFound
puts "No repo found"
exit
end
def execute!
case action
when 'list':
handle_ticket_list
when 'state'
handle_ticket_state
when 'show'
handle_ticket_show
when 'new'
handle_ticket_new
when 'checkout', 'co'
handle_ticket_checkout
when 'comment'
handle_ticket_comment
when 'tag'
handle_ticket_tag
when 'recent'
handle_ticket_recent
when 'milestone'
handle_ticket_milestone
else
puts 'not a command'
end
end
# tic milestone
# tic milestone migration1 (list tickets)
# tic milestone -n migration1 3/4/08 (new milestone)
# tic milestone -a {1} (add ticket to milestone)
# tic milestone -d migration1 (delete)
def parse_ticket_milestone
@options = {}
OptionParser.new do |opts|
opts.banner = "Usage: ti milestone [milestone_name] [options] [date]"
opts.on("-n MILESTONE", "--new MILESTONE", "Add a new milestone to this project") do |v|
@options[:new] = v
end
opts.on("-a TICKET", "--new TICKET", "Add a ticket to this milestone") do |v|
@options[:add] = v
end
opts.on("-d MILESTONE", "--delete MILESTONE", "Remove a milestone") do |v|
@options[:remove] = v
end
end.parse!
end
def handle_ticket_recent
tic.ticket_recent(ARGV[1]).each do |commit|
puts commit.sha[0, 7] + " " + commit.date.strftime("%m/%d %H:%M") + "\t" + commit.message
end
end
def handle_ticket_recent
tic.ticket_recent(ARGV[1]).each do |commit|
puts commit.sha[0, 7] + " " + commit.date.strftime("%m/%d %H:%M") + "\t" + commit.message
end
end
def parse_ticket_tag
@options = {}
OptionParser.new do |opts|
opts.banner = "Usage: ti tag [tic_id] [options] [tag_name] "
opts.on("-d", "Remove this tag from the ticket") do |v|
@options[:remove] = v
end
end.parse!
end
def handle_ticket_tag
parse_ticket_tag
if options[:remove]
puts 'remove'
end
tid = nil
if ARGV.size > 2
tid = ARGV[1].chomp
tic.ticket_tag(ARGV[2].chomp, tid, options)
elsif ARGV.size > 1
tic.ticket_tag(ARGV[1], nil, options)
else
puts 'You need to at least specify one tag to add'
end
end
def parse_ticket_comment
@options = {}
OptionParser.new do |opts|
opts.banner = "Usage: ti comment [tic_id] [options]"
opts.on("-m MESSAGE", "--message MESSAGE", "Message you would like to add as a comment") do |v|
@options[:message] = v
end
end.parse!
end
def handle_ticket_comment
parse_ticket_comment
tid = nil
tid = ARGV[1].chomp if ARGV[1]
if(m = options[:message])
tic.ticket_comment(m, tid)
else
if message = get_editor_message
tic.ticket_comment(message.join(''), tid)
end
end
end
def handle_ticket_checkout
tid = ARGV[1].chomp
tic.ticket_checkout(tid)
end
def handle_ticket_state
if ARGV.size > 2
tid = ARGV[1].chomp
new_state = ARGV[2].chomp
if valid_state(new_state)
tic.ticket_change(new_state, tid)
else
puts 'Invalid State - please choose from : ' + tic.tic_states.join(", ")
end
elsif ARGV.size > 1
# new state
new_state = ARGV[1].chomp
if valid_state(new_state)
tic.ticket_change(new_state)
else
puts 'Invalid State - please choose from : ' + tic.tic_states.join(", ")
end
else
puts 'You need to at least specify a new state for the current ticket'
end
end
def valid_state(state)
tic.tic_states.include?(state)
end
## LIST TICKETS ##
def parse_ticket_list
@options = {}
OptionParser.new do |opts|
opts.banner = "Usage: ti list [options]"
opts.on("-o ORDER", "--order ORDER", "Field to order by - one of : assigned,state,date") do |v|
@options[:order] = v
end
opts.on("-t TAG", "--tag TAG", "List only tickets with specific tag") do |v|
@options[:tag] = v
end
opts.on("-s STATE", "--state STATE", "List only tickets in a specific state") do |v|
@options[:state] = v
end
opts.on("-a ASSIGNED", "--assigned ASSIGNED", "List only tickets assigned to someone") do |v|
@options[:assigned] = v
end
opts.on("-S SAVENAME", "--saveas SAVENAME", "Save this list as a saved name") do |v|
@options[:save] = v
end
opts.on("-l", "--list", "Show the saved queries") do |v|
@options[:list] = true
end
end.parse!
end
def handle_ticket_list
parse_ticket_list
options[:saved] = ARGV[1] if ARGV[1]
if tickets = tic.ticket_list(options)
counter = 0
puts
puts [' ', just('#', 4, 'r'),
just('TicId', 6),
just('Title', 25),
just('State', 5),
just('Date', 5),
just('Assgn', 8),
just('Tags', 20) ].join(" ")
a = []
80.times { a << '-'}
puts a.join('')
tickets.each do |t|
counter += 1
tic.current_ticket == t.ticket_name ? add = '*' : add = ' '
puts [add, just(counter, 4, 'r'),
t.ticket_id[0,6],
just(t.title, 25),
just(t.state, 5),
t.opened.strftime("%m/%d"),
just(t.assigned_name, 8),
just(t.tags.join(','), 20) ].join(" ")
end
puts
end
end
## SHOW TICKETS ##
def handle_ticket_show
if t = @tic.ticket_show(ARGV[1])
ticket_show(t)
end
end
def ticket_show(t)
days_ago = ((Time.now - t.opened) / (60 * 60 * 24)).round.to_s
puts
puts just('Title', 10) + ': ' + t.title
puts just('TicId', 10) + ': ' + t.ticket_id
puts
puts just('Assigned', 10) + ': ' + t.assigned.to_s
puts just('Reporter', 10) + ': ' + t.reporter.to_s
puts just('Opened', 10) + ': ' + t.opened.to_s + ' (' + days_ago + ' days)'
puts just('State', 10) + ': ' + t.state.upcase
if !t.tags.empty?
puts just('Tags', 10) + ': ' + t.tags.join(', ')
end
puts
if !t.comments.empty?
puts 'Comments (' + t.comments.size.to_s + '):'
t.comments.reverse.each do |c|
puts ' * Added ' + c.added.strftime("%m/%d %H:%M") + ' by ' + c.user
wrapped = c.comment.split("\n").collect do |line|
line.length > 80 ? line.gsub(/(.{1,80})(\s+|$)/, "\\1\n").strip : line
end * "\n"
wrapped = wrapped.split("\n").map { |line| "\t" + line }
if wrapped.size > 6
puts wrapped[0, 6].join("\n")
puts "\t** more... **"
else
puts wrapped.join("\n")
end
puts
end
end
end
## NEW TICKETS ##
def parse_ticket_new
@options = {}
OptionParser.new do |opts|
opts.banner = "Usage: ti new [options]"
opts.on("-t TITLE", "--title TITLE", "Title to use for the name of the new ticket") do |v|
@options[:title] = v
end
end.parse!
end
def handle_ticket_new
parse_ticket_new
if(t = options[:title])
ticket_show(@tic.ticket_new(t, options))
else
# interactive
message_file = Tempfile.new('ticgit_message').path
File.open(message_file, 'w') do |f|
f.puts <<ISSUE_FILE
title:
reporter:
#Put your tags after 'tags: ' in comma delimited form
tags:
#put your comment here. remember to indent it
comments: >
ISSUE_FILE
end
if message = get_editor_message(message_file)
puts message
issue_info = YAML.load(File.open(message))
if issue_info.kind_of? String
puts "I couldn't parse that. Make sure that colons " +
"followed by space and your comments are indented"
return
end
title = issue_info['title']
if title && title.chomp.length > 0
tags = issue_info['tags']
tags = tags.split(',').map { |t| t.strip }
comment = issue_info['comments']
ticket_show(@tic.ticket_new(title, :comment => comment, :tags => tags, :reporter => issue_info['reporter']))
else
puts "You need to at least enter a title"
end
end
end
end
def get_editor_message(message_file = nil)
message_file = Tempfile.new('ticgit_message').path if !message_file
editor = ENV["EDITOR"] || 'vim'
system("#{editor} #{message_file}");
return message_file
end
def parse_options! #:nodoc:
if args.empty?
warn "Please specify at least one action to execute."
puts " list state show new checkout comment tag "
exit
end
@action = args.first
end
def just(value, size, side = 'l')
value = value.to_s
if value.size > size
value = value[0, size]
end
if side == 'r'
return value.rjust(size)
else
return value.ljust(size)
end
end
end
end