lib/pt/ui.rb in pt-0.7.3 vs lib/pt/ui.rb in pt-0.8.0

- old
+ new

@@ -1,785 +1,769 @@ require 'yaml' require 'colored' require 'highline' require 'tempfile' +require 'uri' +module PT + class UI -class PT::UI + GLOBAL_CONFIG_PATH = ENV['HOME'] + "/.pt" + LOCAL_CONFIG_PATH = Dir.pwd + '/.pt' - GLOBAL_CONFIG_PATH = ENV['HOME'] + "/.pt" - LOCAL_CONFIG_PATH = Dir.pwd + '/.pt' + def initialize(args) + require 'pt/debugger' if ARGV.delete('--debug') + @io = HighLine.new + @global_config = load_global_config + @client = Client.new(@global_config[:api_number]) + @local_config = load_local_config + @project = @client.get_project(@local_config[:project_id]) + command = args[0].to_sym rescue :my_work + @params = args[1..-1] + commands.include?(command.to_sym) ? send(command.to_sym) : help + end - def initialize(args) - require 'pt/debugger' if ARGV.delete('--debug') - @io = HighLine.new - @global_config = load_global_config - @client = PT::Client.new(@global_config[:api_number]) - @local_config = load_local_config - @project = @client.get_project(@local_config[:project_id]) - command = args[0].to_sym rescue :my_work - @params = args[1..-1] - commands.include?(command.to_sym) ? send(command.to_sym) : help - end + def my_work + title("My Work for #{user_s} in #{project_to_s}") + stories = @client.get_my_work(@project, @local_config[:user_name]) + TasksTable.new(stories).print @global_config + end - def my_work - title("My Work for #{user_s} in #{project_to_s}") - stories = @client.get_my_work(@project, @local_config[:user_name]) - PT::TasksTable.new(stories).print @global_config - end + def todo + title("My Work for #{user_s} in #{project_to_s}") + stories = @client.get_my_work(@project, @local_config[:user_name]) + stories = stories.select { |story| story.current_state == "unscheduled" } + TasksTable.new(stories).print @global_config + end - def todo - title("My Work for #{user_s} in #{project_to_s}") - stories = @client.get_my_work(@project, @local_config[:user_name]) - stories = stories.select { |story| story.current_state == "unscheduled" } - PT::TasksTable.new(stories).print @global_config - end - - def started - # find by a single user - if @params[0] - user = find_owner @params[0] - if user - stories = @project.stories.all(:current_state => 'started', :owned_by => user) - PT::TasksTable.new(stories).print @global_config + %w[unscheduled started finished delivered accepted rejected].each do |state| + define_method(state.to_sym) do + if @params[0] + stories = @project.stories(filter: "owner:#{@params[0]} state:#{state}") + TasksTable.new(stories).print @global_config + else + # otherwise show them all + title("Stories #{state} for #{project_to_s}") + stories = @project.stories(filter:"state:#{state}") + TasksTable.new(stories).print @global_config end - else - # otherwise show them all - title("Stories started for #{project_to_s}") - stories = @project.stories.all(:current_state => 'started') - PT::TasksTable.new(stories).print @global_config + end end - end - def list - if @params[0] - if @params[0] == "all" - stories = @client.get_work(@project) - PT::TasksTable.new(stories).print @global_config - else - user = find_owner @params[0] - if user - stories = @client.get_my_work(@project, user) - PT::TasksTable.new(stories).print @global_config + def list + if @params[0] + if @params[0] == "all" + stories = @client.get_work(@project) + TasksTable.new(stories).print @global_config + else + stories = @client.get_my_work(@project, @params[0]) + TasksTable.new(stories).print @global_config end + else + members = @client.get_members(@project) + table = MembersTable.new(members) + user = select("Please select a member to see his tasks.", table).name + title("Work for #{user} in #{project_to_s}") + stories = @client.get_my_work(@project, user) + TasksTable.new(stories).print @global_config end - else - members = @client.get_members(@project) - table = PT::MembersTable.new(members) - user = select("Please select a member to see his tasks.", table).name - title("Work for #{user} in #{project_to_s}") - stories = @client.get_my_work(@project, user) - PT::TasksTable.new(stories).print @global_config end - end - def recent - title("Your recent stories from #{project_to_s}") - stories = @project.stories.all( :id => @local_config[:recent_tasks] ) - PT::MultiUserTasksTable.new(stories).print @global_config - end + def recent + title("Your recent stories from #{project_to_s}") + stories = @project.stories( ids: @local_config[:recent_tasks].join(',') ) + MultiUserTasksTable.new(stories).print @global_config + end - def label + def label - task = get_task_from_params "Please select a story to show" - unless task - message("No matches found for '#{@params[0]}', please use a valid pivotal story Id") - return - end + task = get_task_from_params "Please select a story to show" + unless task + message("No matches found for '#{@params[0]}', please use a valid pivotal story Id") + return + end - if @params[1] - label = @params[1] - else - label = ask("Which label?") + if @params[1] + label = @params[1] + else + label = ask("Which label?") + end + + @client.add_label( @project, task, label ); + show_task(task_by_id_or_pt_id(task.id)) end - @client.add_label( @project, task, label ); + def create + if @params[0] + name = @params[0] + owner = @params[1] || @local_config[:user_name] + requester = @local_config[:user_name] + task_type = task_type_or_nil(@params[1]) || task_type_or_nil(@params[2]) || 'feature' + else + title("Let's create a new task:") + name = ask("Name for the new task:") + end - end + owner = @client.find_member(@project, owner).person.id if owner.kind_of?(String) - def create - if @params[0] - name = @params[0] - owner = find_owner(@params[1]) || find_owner(@params[2]) || @local_config[:user_name] - requester = @local_config[:user_name] - task_type = task_type_or_nil(@params[1]) || task_type_or_nil(@params[2]) || 'feature' - else - title("Let's create a new task:") - name = ask("Name for the new task:") - end + unless owner + if ask('Do you want to assign it now? (y/n)').downcase == 'y' + members = @client.get_members(@project) + table = PersonsTable.new(members.map(&:person)) + owner = select("Please select a member to assign him the task.", table).id + else + owner = nil + end + requester = @local_config[:user_name] + task_type = ask('Type? (c)hore, (b)ug, anything else for feature)') + end - unless owner - if ask('Do you want to assign it now? (y/n)').downcase == 'y' - members = @client.get_members(@project) - table = PT::MembersTable.new(members) - owner = select("Please select a member to assign him the task.", table).name + task_type = case task_type + when 'c', 'chore' + 'chore' + when 'b', 'bug' + 'bug' + else + 'feature' + end + result = nil + + owner_ids = [owner] + # did you do a -m so you can add a description? + if ARGV.include? "-m" or ARGV.include? "--m" + editor = ENV.fetch('EDITOR') { 'vi' } + temp_path = "/tmp/editor-#{ Process.pid }.txt" + system "#{ editor } #{ temp_path }" + + description = File.read(temp_path) + story = @client.create_task_with_description(@project, name, owner_ids, task_type, description) else - owner = nil + story = @client.create_task(@project, name, owner_ids, task_type) end - requester = @local_config[:user_name] - task_type = ask('Type? (c)hore, (b)ug, anything else for feature)') + # TODO need result + congrats("#{task_type} for #{owner} open #{story.url}") end - task_type = case task_type - when 'c', 'chore' - 'chore' - when 'b', 'bug' - 'bug' - else - 'feature' + def open + if @params[0] + if task = @client.get_task_by_id(@project, @params[0]) + congrats("Opening #{task.name}") + `open #{task.url}` + else + message("Story ##{@params[0]} not found") + end + else + tasks = @client.get_my_open_tasks(@project, @local_config[:user_name]) + table = TasksTable.new(tasks) + title("Tasks for #{user_s} in #{project_to_s}") + task = select("Please select a story to open it in the browser", table) + end end - result = nil - # did you do a -m so you can add a description? - if ARGV.include? "-m" or ARGV.include? "--m" - editor = ENV.fetch('EDITOR') { 'vi' } - temp_path = "/tmp/editor-#{ Process.pid }.txt" - system "#{ editor } #{ temp_path }" - - description = File.read(temp_path) - result = @client.create_task_with_description(@project, name, owner, requester, task_type, description) - - else - result = @client.create_task(@project, name, owner, requester, task_type) + def comment + if @params[0] + task = task_by_id_or_pt_id @params[0].to_i + comment = @params[1] + title("Adding a comment to #{task.name}") + else + tasks = @client.get_my_work(@project, @local_config[:user_name]) + table = TasksTable.new(tasks) + title("Tasks for #{user_s} in #{project_to_s}") + task = select("Please select a story to comment it", table) + comment = ask("Write your comment") + end + if @client.comment_task(@project, task, comment) + congrats("Comment sent, thanks!") + save_recent_task( task.id ) + else + error("Ummm, something went wrong.") + end end - - if result.errors.any? - error(result.errors.errors) - else - congrats("#{task_type} for #{owner} created: #{result.url}") - end - end - def open - if @params[0] - tasks = @client.get_my_work(@project, @local_config[:user_name]) - table = PT::TasksTable.new(tasks) - task = table[ @params[0].to_i ] - congrats("Opening #{task.name}") - else - tasks = @client.get_my_open_tasks(@project, @local_config[:user_name]) - table = PT::TasksTable.new(tasks) - title("Tasks for #{user_s} in #{project_to_s}") - task = select("Please select a story to open it in the browser", table) - end - `open #{task.url}` - end + def assign + if @params[0] + task = task_by_id_or_pt_id @params[0].to_i + owner = find_owner @params[1] + else + title("Tasks for #{user_s} in #{project_to_s}") + tasks = @client.get_tasks_to_assign(@project) + table = TasksTable.new(tasks) + task = select("Please select a task to assign it an owner", table) + end - def comment - if @params[0] - task = task_by_id_or_pt_id @params[0].to_i - comment = @params[1] - title("Adding a comment to #{task.name}") - else - tasks = @client.get_my_work(@project, @local_config[:user_name]) - table = PT::TasksTable.new(tasks) - title("Tasks for #{user_s} in #{project_to_s}") - task = select("Please select a story to comment it", table) - comment = ask("Write your comment") - end - if @client.comment_task(@project, task, comment) - congrats("Comment sent, thanks!") - save_recent_task( task.id ) - else - error("Ummm, something went wrong.") - end - end + unless owner + members = @client.get_members(@project) + table = PersonsTable.new(members.map(&:person)) + owner = select("Please select a member to assign him the task", table) + end + @client.assign_task(@project, task, owner) - def assign - if @params[0] - task = task_by_id_or_pt_id @params[0].to_i - owner = find_owner @params[1] - else - title("Tasks for #{user_s} in #{project_to_s}") - tasks = @client.get_tasks_to_assign(@project, @local_config[:user_name]) - table = PT::TasksTable.new(tasks) - task = select("Please select a task to assign it an owner", table) + congrats("Task assigned to #{owner.initials}, thanks!") end - - unless owner - members = @client.get_members(@project) - table = PT::MembersTable.new(members) - owner = select("Please select a member to assign him the task", table).name - end - result = @client.assign_task(@project, task, owner) - - if result.errors.any? - error(result.errors.errors) - else - congrats("Task assigned to #{owner}, thanks!") - end - end - def estimate - if @params[0] - task = task_by_id_or_pt_id @params[0].to_i - title("Estimating '#{task.name}'") + def estimate + if @params[0] + task = task_by_id_or_pt_id @params[0].to_i + title("Estimating '#{task.name}'") - if [0,1,2,3].include? @params[1].to_i - estimation = @params[1] + if [0,1,2,3].include? @params[1].to_i + estimation = @params[1] + end + else + tasks = @client.get_my_tasks_to_estimate(@project, @local_config[:user_name]) + table = TasksTable.new(tasks) + title("Tasks for #{user_s} in #{project_to_s}") + task = select("Please select a story to estimate it", table) end - else - tasks = @client.get_my_tasks_to_estimate(@project, @local_config[:user_name]) - table = PT::TasksTable.new(tasks) - title("Tasks for #{user_s} in #{project_to_s}") - task = select("Please select a story to estimate it", table) - end - estimation ||= ask("How many points you estimate for it? (#{@project.point_scale})") - result = @client.estimate_task(@project, task, estimation) - if result.errors.any? - error(result.errors.errors) - else + estimation ||= ask("How many points you estimate for it? (#{@project.point_scale})") + @client.estimate_task(@project, task, estimation) congrats("Task estimated, thanks!") end - end - def start - if @params[0] - task = task_by_id_or_pt_id @params[0].to_i - title("Starting '#{task.name}'") - else - tasks = @client.get_my_tasks_to_start(@project, @local_config[:user_name]) - table = PT::TasksTable.new(tasks) - title("Tasks for #{user_s} in #{project_to_s}") - task = select("Please select a story to mark it as started", table) + def start + if @params[0] + task = task_by_id_or_pt_id @params[0].to_i + title("Starting '#{task.name}'") + else + tasks = @client.get_my_tasks_to_start(@project, @local_config[:user_name]) + table = TasksTable.new(tasks) + title("Tasks for #{user_s} in #{project_to_s}") + task = select("Please select a story to mark it as started", table) + end + start_task task end - start_task task - end - def finish - if @params[0] - task = task_by_id_or_pt_id @params[0].to_i - title("Finishing '#{task.name}'") - else - tasks = @client.get_my_tasks_to_finish(@project, @local_config[:user_name]) - table = PT::TasksTable.new(tasks) - title("Tasks for #{user_s} in #{project_to_s}") - task = select("Please select a story to mark it as finished", table) + def finish + if @params[0] + task = task_by_id_or_pt_id @params[0].to_i + title("Finishing '#{task.name}'") + else + tasks = @client.get_my_tasks_to_finish(@project, @local_config[:user_name]) + table = TasksTable.new(tasks) + title("Tasks for #{user_s} in #{project_to_s}") + task = select("Please select a story to mark it as finished", table) + end + finish_task task end - finish_task task - end - def deliver - if @params[0] - task = task_by_id_or_pt_id @params[0].to_i - title("Delivering '#{task.name}'") - else - tasks = @client.get_my_tasks_to_deliver(@project, @local_config[:user_name]) - table = PT::TasksTable.new(tasks) - title("Tasks for #{user_s} in #{project_to_s}") - task = select("Please select a story to mark it as delivered", table) - end + def deliver + if @params[0] + task = task_by_id_or_pt_id @params[0].to_i + title("Delivering '#{task.name}'") + else + tasks = @client.get_my_tasks_to_deliver(@project, @local_config[:user_name]) + table = TasksTable.new(tasks) + title("Tasks for #{user_s} in #{project_to_s}") + task = select("Please select a story to mark it as delivered", table) + end - deliver_task task - end - - def accept - if @params[0] - task = task_by_id_or_pt_id @params[0].to_i - title("Accepting '#{task.name}'") - else - tasks = @client.get_my_tasks_to_accept(@project, @local_config[:user_name]) - table = PT::TasksTable.new(tasks) - title("Tasks for #{user_s} in #{project_to_s}") - task = select("Please select a story to mark it as accepted", table) + deliver_task task end - result = @client.mark_task_as(@project, task, 'accepted') - if result.errors.any? - error(result.errors.errors) - else + + def accept + if @params[0] + task = task_by_id_or_pt_id @params[0].to_i + title("Accepting '#{task.name}'") + else + tasks = @client.get_my_tasks_to_accept(@project, @local_config[:user_name]) + table = TasksTable.new(tasks) + title("Tasks for #{user_s} in #{project_to_s}") + task = select("Please select a story to mark it as accepted", table) + end + @client.mark_task_as(@project, task, 'accepted') congrats("Task accepted, hooray!") end - end - def show - title("Tasks for #{user_s} in #{project_to_s}") - task = get_task_from_params "Please select a story to show" - unless task - message("No matches found for '#{@params[0]}', please use a valid pivotal story Id") - return + def show + title("Tasks for #{user_s} in #{project_to_s}") + task = get_task_from_params "Please select a story to show" + unless task + message("No matches found for '#{@params[0]}', please use a valid pivotal story Id") + return + end + show_task(task) end - result = show_task(task) - end - def tasks - title("Open story tasks for #{user_s} in #{project_to_s}") - task = get_task_from_params "Please select a story to show pending tasks" - unless task - message("No matches found for '#{@params[0]}', please use a valid pivotal story Id") - return - end - story_task = get_open_story_task_from_params(task) + def tasks + title("Open story tasks for #{user_s} in #{project_to_s}") - if story_task.position == -1 - description = ask('Title for new task') - task.tasks.create(:description => description) - congrats("New todo task added to \"#{task.name}\"") - else - edit_story_task story_task + unless story = get_task_from_params( "Please select a story to show pending tasks" ) + message("No matches found for '#{@params[0]}', please use a valid pivotal story Id") + return + end + + story_task = get_open_story_task_from_params(story) + + if story_task.position == -1 + description = ask('Title for new task') + story.create_task(:description => description) + congrats("New todo task added to \"#{story.name}\"") + else + edit_story_task story_task + end end - end - # takes a comma separated list of ids and prints the collection of tasks - def show_condensed - title("Tasks for #{user_s} in #{project_to_s}") + # takes a comma separated list of ids and prints the collection of tasks + def show_condensed + title("Tasks for #{user_s} in #{project_to_s}") tasks = [] - if @params[0] - @params[0].each_line(',') do |line| - tasks << @client.get_task_by_id(line.to_i) + if @params[0] + @params[0].each_line(',') do |line| + tasks << @client.get_task_by_id(@project, line.to_i) + end + table = TasksTable.new(tasks) + table.print end - table = PT::TasksTable.new(tasks) - table.print end - end - def reject - title("Tasks for #{user_s} in #{project_to_s}") - if @params[0] - tasks = @client.get_my_work(@project, @local_config[:user_name]) - table = PT::TasksTable.new(tasks) - task = table[@params[0].to_i] - title("Rejecting '#{task.name}'") - else - tasks = @client.get_my_tasks_to_reject(@project, @local_config[:user_name]) - table = PT::TasksTable.new(tasks) + # TODO implement story notes and comment + def reject title("Tasks for #{user_s} in #{project_to_s}") - task = select("Please select a story to mark it as rejected", table) - end + if @params[0] + task = @client.get_story(@project, @params[0]) + title("Rejecting '#{task.name}'") + else + tasks = @client.get_my_tasks_to_reject(@project, @local_config[:user_name]) + table = TasksTable.new(tasks) + title("Tasks for #{user_s} in #{project_to_s}") + task = select("Please select a story to mark it as rejected", table) + end - if @params[1] - comment = @params[1] - else - comment = ask("Please explain why are you rejecting the task.") - end + if @params[1] + comment = @params[1] + else + comment = ask("Please explain why are you rejecting the task.") + end - if @client.comment_task(@project, task, comment) - result = @client.mark_task_as(@project, task, 'rejected') - congrats("Task rejected, thanks!") - else - error("Ummm, something went wrong.") + if @client.comment_task(@project, task, comment) + result = @client.mark_task_as(@project, task, 'rejected') + congrats("Task rejected, thanks!") + else + error("Ummm, something went wrong.") + end end - end - def done - if @params[0] - task = task_by_id_or_pt_id @params[0].to_i + def done + if @params[0] + task = task_by_id_or_pt_id @params[0].to_i - #we need this for finding again later - task_id = task.id + #we need this for finding again later + task_id = task.id - if !@params[1] && task.estimate == -1 - error("You need to give an estimate for this task") - return - end + if !@params[1] && task.estimate == -1 + error("You need to give an estimate for this task") + return + end - if @params[1] && task.estimate == -1 + if @params[1] && task.estimate == -1 if [0,1,2,3].include? @params[1].to_i estimate_task(task, @params[1].to_i) end if @params[2] task = task_by_id_or_pt_id task_id @client.comment_task(@project, task, @params[2]) end - else - @client.comment_task(@project, task, @params[1]) if @params[1] - end + else + @client.comment_task(@project, task, @params[1]) if @params[1] + end - task = task_by_id_or_pt_id task_id - start_task task + task = task_by_id_or_pt_id task_id + start_task task - task = task_by_id_or_pt_id task_id - finish_task task + task = task_by_id_or_pt_id task_id + finish_task task - task = task_by_id_or_pt_id task_id - deliver_task task + task = task_by_id_or_pt_id task_id + deliver_task task + end end - end - def estimate_task task, difficulty - result = @client.estimate_task(@project, task, difficulty) - if result.errors.any? - error(result.errors.errors) - else - congrats("Task estimated, thanks!") + def estimate_task task, difficulty + result = @client.estimate_task(@project, task, difficulty) + if result.errors.any? + error(result.errors.errors) + else + congrats("Task estimated, thanks!") + end end - end - def start_task task - result = @client.mark_task_as(@project, task, 'started') - if result.errors.any? - error(result.errors.errors) - else + def start_task task + @client.mark_task_as(@project, task, 'started') congrats("Task started, go for it!") end - end - def finish_task task - if task.story_type == 'chore' - result = @client.mark_task_as(@project, task, 'accepted') - else - result = @client.mark_task_as(@project, task, 'finished') - end - if result.errors.any? - error(result.errors.errors) - else + def finish_task task + if task.story_type == 'chore' + @client.mark_task_as(@project, task, 'accepted') + else + @client.mark_task_as(@project, task, 'finished') + end congrats("Another task bites the dust, yeah!") end - end - def deliver_task task - return if task.story_type == 'chore' - - result = @client.mark_task_as(@project, task, 'delivered') - error(result.errors.errors) if result.errors.any? - if result.errors.any? - error(result.errors.errors) - else + def deliver_task task + return if task.story_type == 'chore' + @client.mark_task_as(@project, task, 'delivered') congrats("Task delivered, congrats!") end - end - def find - if (story_id = @params[0].to_i).nonzero? - if task = task_by_id_or_pt_id(@params[0].to_i) - return show_task(task) + def find + if (story_id = @params[0].to_i).nonzero? + if task = task_by_id_or_pt_id(@params[0].to_i) + return show_task(task) + else + message("Task not found by id (#{story_id}), falling back to text search") + end + end + + if @params[0] + tasks = @client.search_for_story(@project, @params[0]) + tasks.each do |story_task| + title("--- [#{(tasks.index story_task) + 1 }] -----------------") + show_task(story_task) + end + message("No matches found for '#{@params[0]}'") if tasks.empty? else - message("Task not found by id (#{story_id}), falling back to text search") + message("You need to provide a substring for a tasks title.") end end - if @params[0] + def updates + activities = @client.get_activities(@project, @params[0]) tasks = @client.get_my_work(@project, @local_config[:user_name]) - matched_tasks = tasks.select do |story_task| - story_task.name.downcase.index(@params[0]) && story_task.current_state != 'delivered' + title("Recent Activity on #{project_to_s}") + activities.each do |activity| + show_activity(activity, tasks) end + end - matched_tasks.each do |story_task| - title("--- [#{(tasks.index story_task) + 1 }] -----------------") - show_task(story_task) + + def help + if ARGV[0] && ARGV[0] != 'help' + message("Command #{ARGV[0]} not recognized. Showing help.") end - message("No matches found for '#{@params[0]}'") if matched_tasks.empty? - else - message("You need to provide a substring for a tasks title.") - end - end - def updates - activities = @client.get_activities(@project, @params[0]) - tasks = @client.get_my_work(@project, @local_config[:user_name]) - title("Recent Activity on #{project_to_s}") - activities.each do |activity| - show_activity(activity, tasks) - end - end + title("Command line usage for pt #{VERSION}") + help = <<-HELP + pt # show all available stories + pt todo <owner> # show all unscheduled stories - def help - if ARGV[0] && ARGV[0] != 'help' - message("Command #{ARGV[0]} not recognized. Showing help.") + pt (unscheduled,started,finished,delivered, accepted, rejected) <owner> # show all (unscheduled,started,finished,delivered, accepted, rejected) stories + + pt create [title] <owner> <type> -m # create a new story (and include description ala git commit) + + pt show [id] # shows detailed info about a story + + pt tasks [id] # manage tasks of story + + pt open [id] # open a story in the browser + + pt assign [id] <owner> # assign owner + + pt comment [id] [comment] # add a comment + + pt label [id] [label] # add a label + + pt estimate [id] [0-3] # estimate a story in points scale + + pt (start,finish,deliver,accept) [id] # mark a story as started + + pt reject [id] [reason] # mark a story as rejected, explaining why + + pt done [id] <0-3> <comment> # lazy mans finish story, opens, assigns to you, estimates, finish & delivers + + pt find [query] # looks in your stories by title and presents it + + pt list [owner] # list all stories for another pt user + + pt list all # list all stories for all users + + pt updates # shows number recent activity from your current project + + pt recent # shows stories you've recently shown or commented on with pt + + All commands can be run entirely without arguments for a wizard based UI. Otherwise [required] <optional>. + Anything that takes an id will also take the num (index) from the pt command. + HELP + puts(help) end - title("Command line usage for pt #{PT::VERSION}") - puts("pt # show all available tasks") - puts("pt todo <owner> # show all unscheduled tasks") - puts("pt started <owner> # show all started stories") - puts("pt create [title] <owner> <type> -m # create a new task (and include description ala git commit)") - puts("pt show [id] # shows detailed info about a task") - puts("pt tasks [id] # manage tasks of story") - puts("pt open [id] # open a task in the browser") - puts("pt assign [id] <owner> # assign owner") - puts("pt comment [id] [comment] # add a comment") - puts("pt label [id] [label] # add a label") - puts("pt estimate [id] [0-3] # estimate a task in points scale") - puts("pt start [id] # mark a task as started") - puts("pt finish [id] # indicate you've finished a task") - puts("pt deliver [id] # indicate the task is delivered"); - puts("pt accept [id] # mark a task as accepted") - puts("pt reject [id] [reason] # mark a task as rejected, explaining why") - puts("pt done [id] <0-3> <comment> # lazy mans finish task, opens, assigns to you, estimates, finish & delivers") - puts("pt find [query] # looks in your tasks by title and presents it") - puts("pt list [owner] or all # list all tasks for another pt user") - puts("pt updates [number] # shows number recent activity from your current project") - puts("pt recent # shows stories you've recently shown or commented on with pt") - puts("") - puts("All commands can be run entirely without arguments for a wizard based UI. Otherwise [required] <optional>.") - puts("Anything that takes an id will also take the num (index) from the pt command.") - end + protected - protected + def commands + (public_methods - Object.public_methods).map{ |c| c.to_sym} + end - def commands - (public_methods - Object.public_methods).map{ |c| c.to_sym} - end + # Config - # Config + def load_global_config - def load_global_config - config = YAML.load(File.read(GLOBAL_CONFIG_PATH)) rescue {} - if config.empty? - message "I can't find info about your Pivotal Tracker account in #{GLOBAL_CONFIG_PATH}." - while !config[:api_number] do - config[:email] = ask "What is your email?" - password = ask_secret "And your password? (won't be displayed on screen)" - begin - config[:api_number] = PT::Client.get_api_token(config[:email], password) - rescue PT::InputError => e - error e.message + " Please try again." + # skip global config if env vars are set + if ENV['PIVOTAL_EMAIL'] and ENV['PIVOTAL_API_KEY'] + config = { + :email => ENV['PIVOTAL_EMAIL'], + :api_number => ENV['PIVOTAL_API_KEY'] + } + return config + end + + config = YAML.load(File.read(GLOBAL_CONFIG_PATH)) rescue {} + if config.empty? + message "I can't find info about your Pivotal Tracker account in #{GLOBAL_CONFIG_PATH}." + while !config[:api_number] do + config[:api_number] = ask "What is your token?" end + congrats "Thanks!", + "Your API id is " + config[:api_number], + "I'm saving it in #{GLOBAL_CONFIG_PATH} so you don't have to log in again." + save_config(config, GLOBAL_CONFIG_PATH) end - congrats "Thanks!", - "Your API id is " + config[:api_number], - "I'm saving it in #{GLOBAL_CONFIG_PATH} so you don't have to log in again." - save_config(config, GLOBAL_CONFIG_PATH) + config end - config - end - def load_local_config - check_local_config_path - config = YAML.load(File.read(LOCAL_CONFIG_PATH)) rescue {} - if config.empty? - message "I can't find info about this project in #{LOCAL_CONFIG_PATH}" - projects = PT::ProjectTable.new(@client.get_projects) - project = select("Please select the project for the current directory", projects) - config[:project_id], config[:project_name] = project.id, project.name - project = @client.get_project(project.id) - membership = @client.get_membership(project, @global_config[:email]) - config[:user_name], config[:user_id], config[:user_initials] = membership.name, membership.id, membership.initials - congrats "Thanks! I'm saving this project's info", - "in #{LOCAL_CONFIG_PATH}: remember to .gitignore it!" - save_config(config, LOCAL_CONFIG_PATH) + def get_local_config_path + # If the local config path does not exist, check to see if we're in a git repo + # And if so, try the top level of the checkout + if (!File.exist?(LOCAL_CONFIG_PATH) && system('git rev-parse 2> /dev/null')) + return `git rev-parse --show-toplevel`.chomp() + '/.pt' + else + return LOCAL_CONFIG_PATH + end end - config - end - def check_local_config_path - if GLOBAL_CONFIG_PATH == LOCAL_CONFIG_PATH - error("Please execute .pt inside your project directory and not in your home.") - exit + def load_local_config + check_local_config_path + config = YAML.load(File.read(get_local_config_path())) rescue {} + + if ENV['PIVOTAL_PROJECT_ID'] + + config[:project_id] = ENV['PIVOTAL_PROJECT_ID'] + + project = @client.get_project(config[:project_id]) + config[:project_name] = project.name + + membership = @client.get_my_info + config[:user_name], config[:user_id], config[:user_initials] = membership.name, membership.id, membership.initials + save_config(config, get_local_config_path()) + + end + + if config.empty? + message "I can't find info about this project in #{get_local_config_path()}" + projects = ProjectTable.new(@client.get_projects) + project = select("Please select the project for the current directory", projects) + config[:project_id], config[:project_name] = project.id, project.name + project = @client.get_project(project.id) + membership = @client.get_my_info + config[:user_name], config[:user_id], config[:user_initials] = membership.name, membership.id, membership.initials + congrats "Thanks! I'm saving this project's info", + "in #{get_local_config_path()}: remember to .gitignore it!" + save_config(config, get_local_config_path()) + end + config end - end - def save_config(config, path) - File.new(path, 'w') unless File.exists?(path) - File.open(path, 'w') {|f| f.write(config.to_yaml) } - end + def check_local_config_path + if GLOBAL_CONFIG_PATH == get_local_config_path() + error("Please execute .pt inside your project directory and not in your home.") + exit + end + end - # I/O + def save_config(config, path) + File.new(path, 'w') unless File.exists?(path) + File.open(path, 'w') {|f| f.write(config.to_yaml) } + end - def split_lines(text) - text.respond_to?(:join) ? text.join("\n") : text - end + # I/O - def title(*msg) - puts "\n#{split_lines(msg)}".bold - end + def split_lines(text) + text.respond_to?(:join) ? text.join("\n") : text + end - def congrats(*msg) - puts "\n#{split_lines(msg).green.bold}" - end + def title(*msg) + puts "\n#{split_lines(msg)}".bold + end - def message(*msg) - puts "\n#{split_lines(msg)}" - end + def congrats(*msg) + puts "\n#{split_lines(msg).green.bold}" + end - def compact_message(*msg) - puts "#{split_lines(msg)}" - end + def message(*msg) + puts "\n#{split_lines(msg)}" + end - def error(*msg) - puts "\n#{split_lines(msg).red.bold}" - end + def compact_message(*msg) + puts "#{split_lines(msg)}" + end - def select(msg, table) - if table.length > 0 - begin + def error(*msg) + puts "\n#{split_lines(msg).red.bold}" + end + + def select(msg, table) + if table.length > 0 + begin + table.print @global_config + row = ask "#{msg} (1-#{table.length}, 'q' to exit)" + quit if row == 'q' + selected = table[row] + error "Invalid selection, try again:" unless selected + end until selected + selected + else table.print @global_config - row = ask "#{msg} (1-#{table.length}, 'q' to exit)" - quit if row == 'q' - selected = table[row] - error "Invalid selection, try again:" unless selected - end until selected - selected - else - table.print @global_config - message "Sorry, there are no options to select." - quit + message "Sorry, there are no options to select." + quit + end end - end - def quit - message "bye!" - exit - end + def quit + message "bye!" + exit + end - def ask(msg) - @io.ask("#{msg.bold}") - end + def ask(msg) + @io.ask("#{msg.bold}") + end - def ask_secret(msg) - @io.ask("#{msg.bold}"){ |q| q.echo = '*' } - end + def ask_secret(msg) + @io.ask("#{msg.bold}"){ |q| q.echo = '*' } + end - def user_s - "#{@local_config[:user_name]} (#{@local_config[:user_initials]})" - end + def user_s + "#{@local_config[:user_name]} (#{@local_config[:user_initials]})" + end - def project_to_s - "Project #{@local_config[:project_name].upcase}" - end + def project_to_s + "Project #{@local_config[:project_name].upcase}" + end - def task_type_or_nil query - if (["feature", "bug", "chore"].index query) - return query + def task_type_or_nil query + if (["feature", "bug", "chore"].index query) + return query + end + nil end - nil - end - def task_by_id_or_pt_id id - if id < 1000 - tasks = @client.get_my_work(@project, @local_config[:user_name]) - table = PT::TasksTable.new(tasks) - table[id] - else - @client.get_task_by_id id + def task_by_id_or_pt_id id + if id < 1000 + tasks = @client.get_my_work(@project, @local_config[:user_name]) + table = TasksTable.new(tasks) + table[id] + else + @client.get_task_by_id @project, id + end end - end - def find_task query - members = @client.get_members(@project) - members.each do | member | - if member.name.downcase.index query - return member.name + def find_task query + members = @client.get_members(@project) + members.each do | member | + if member.name.downcase.index query + return member.name + end end + nil end - nil - end - def find_owner query - if query - member = @client.get_member(@project, query) - return member ? member.name : nil + def find_owner query + if query + member = @client.get_member(@project, query) + return member ? member.person : nil + end + nil end - nil - end - def show_task(task) - title task.name.green - estimation = [-1, nil].include?(task.estimate) ? "Unestimated" : "#{task.estimate} points" - message "#{task.current_state.capitalize} #{task.story_type} | #{estimation} | Req: #{task.requested_by} | Owns: #{task.owned_by} | Id: #{task.id}" + def show_task(task) + title task.name.green + estimation = [-1, nil].include?(task.estimate) ? "Unestimated" : "#{task.estimate} points" + message "#{task.current_state.capitalize} #{task.story_type} | #{estimation} | Req: #{task.requested_by.initials} | + Owners: #{task.owners.map(&:initials).join(',')} | Id: #{task.id}" if (task.labels) - message "Labels: " + task.labels.split(',').join(', ') + message "Labels: " + task.labels.map(&:name).join(', ') end message task.description unless task.description.nil? || task.description.empty? message "View on pivotal: #{task.url}" if task.tasks - task.tasks.all.each{ |t| compact_message "- #{t.complete ? "(done) " : "(pend)"} #{t.description}" } + title('tasks'.red) + task.tasks.each{ |t| compact_message "- #{t.complete ? "(v) " : "( )"} #{t.description}" } end - # attachments on a note come through with the same description as the note - # to prevent the same update from showing multiple times, arrange by description for later lookup - attachment_match = Hash.new() - task.attachments.each do |a| - unless attachment_match[ a.description ] - attachment_match[ a.description ] = Array.new() - end - attachment_match[ a.description ].push( a ); + task.comments.each do |n| + title('========================================='.red) + message ">> #{n.person.initials}: #{n.text} [#{n.file_attachment_ids.size}F]" end - - task.notes.all.each do |n| - message "#{n.author.yellow}: #{n.text}" - # print attachements for this note - if attachment_match[ n.text ] - message "Attachments".bold - attachment_match[ n.text ].each{ |a| message "#{a.filename} #{a.url}" } - attachment_match.delete(n.text) - end + save_recent_task( task.id ) end - task.attachments.each do |a| - # skip attachments already printed as part of a note - if attachment_match[ a.description ] - message "#{a.uploaded_by.yellow} uploaded: \"#{a.description && a.description.empty? ? "#{a.filename}" : "#{a.description} (#{a.filename})" }\" #{a.url}" - end + + def show_activity(activity, tasks) + message("#{activity.message}") end - save_recent_task( task.id ) + def get_open_story_task_from_params(task) + title "Pending tasks for '#{task.name}'" + task_struct = Struct.new(:description, :position) - end + pending_tasks = [ + task_struct.new('<< Add new task >>', -1) + ] + task.tasks.each{ |t| pending_tasks << t unless t.complete } + table = TodoTaskTable.new(pending_tasks) + select("Pick task to edit, 1 to add new task", table) + end - def show_activity(activity, tasks) - story_id = activity.stories.first.id - task_id = nil - tasks.each do |story| - if story_id == story.id - task_id = tasks.index(story) + def get_task_from_params(prompt) + if @params[0] + task = task_by_id_or_pt_id(@params[0].to_i) + else + tasks = @client.get_all_stories(@project, @local_config[:user_name]) + table = TasksTable.new(tasks) + task = select(prompt, table) end end - message("#{activity.description} [#{task_id}]") - end - def get_open_story_task_from_params(task) - title "Pending tasks for '#{task.name}'" - task_struct = Struct.new(:description, :position) + def edit_story_task(story_task) + action_class = Struct.new(:action, :key) - pending_tasks = [ - task_struct.new('<< Add new task >>', -1) - ] + table = ActionTable.new([ + action_class.new('Complete', :complete), + action_class.new('Delete', :delete), + action_class.new('Edit', :edit) + # Move? + ]) + action_to_execute = select('What to do with todo?', table) - task.tasks.all.each{ |t| pending_tasks << t unless t.complete } - table = PT::TodoTaskTable.new(pending_tasks) - todo_task = select("Pick task to edit, 1 to add new task", table) - end - - def get_task_from_params(prompt) - if @params[0] - task = task_by_id_or_pt_id(@params[0].to_i) - else - tasks = @client.get_my_work(@project, @local_config[:user_name]) - table = PT::TasksTable.new(tasks) - task = select(prompt, table) + case action_to_execute.key + when :complete then + story_task.update(:complete => true) + congrats('Todo task completed!') + when :delete then + story_task.delete + congrats('Todo task removed') + when :edit then + new_description = ask('New task description') + story_task.update(:description => new_description) + congrats("Todo task changed to: \"#{story_task.description}\"") + end end - end - - def edit_story_task(story_task) - action_class = Struct.new(:action, :key) - table = PT::ActionTable.new([ - action_class.new('Complete', :complete), - action_class.new('Delete', :delete), - action_class.new('Edit', :edit) - # Move? - ]) - action_to_execute = select('What to do with todo?', table) - - case action_to_execute.key - when :complete then - story_task.update(:complete => true) - congrats('Todo task completed!') - when :delete then - story_task.delete - congrats('Todo task removed') - when :edit then - new_description = ask('New task description') - story_task.update(:description => new_description) - congrats("Todo task changed to: \"#{story_task.description}\"") + def save_recent_task( task_id ) + # save list of recently accessed tasks + unless (@local_config[:recent_tasks]) + @local_config[:recent_tasks] = Array.new(); + end + @local_config[:recent_tasks].unshift( task_id ) + @local_config[:recent_tasks] = @local_config[:recent_tasks].uniq() + if @local_config[:recent_tasks].length > 10 + @local_config[:recent_tasks].pop() + end + save_config( @local_config, get_local_config_path() ) end - end - def save_recent_task( task_id ) - # save list of recently accessed tasks - unless (@local_config[:recent_tasks]) - @local_config[:recent_tasks] = Array.new(); - end - @local_config[:recent_tasks].unshift( task_id ) - @local_config[:recent_tasks] = @local_config[:recent_tasks].uniq() - if @local_config[:recent_tasks].length > 10 - @local_config[:recent_tasks].pop() - end - save_config( @local_config, LOCAL_CONFIG_PATH ) end - end