lib/tracking/cli.rb
require 'optparse'
require 'colorize'
module Tracking
# Contains methods for displaying the list in a command line and parsing
# command line arguments.
module CLI
extend self
# Width of the first column (start time)
@start_time_width = 5
# Width of the second column (name)
@name_width = TrackingConfig[:task_width]
# Width of the third column (elapsed time)
@elapsed_time_width = Task.elapsed_time_length
# Displays part of the list in the command line
#
# @param [Hash] options the options to use for retrieving tasks (passed to
# List#get)
def display options={}
display_object :top
tasks = List.get options
if tasks.length > 0
tasks.each_with_index do |task, task_index|
display_task(task)
end
else
display_object :intro
end
display_object :bottom
end
# Displays a single formatted task in the command line
#
# @param [Task] task the task to display
def display_task(task)
split_task(task.name).each_with_index do |name_line, line_index|
col_1 = pad((task.start_time if line_index==0), 5)
col_2 = pad(name_line, @name_width)
col_3 = pad((task.elapsed_time if line_index==0), @elapsed_time_width)
if task.current? and TrackingConfig[:color_current_task]
current_task_color = TrackingConfig[:current_task_color]
current_task_color = TrackingConfig.defaults[:current_task_color] unless String.colors.include? current_task_color
col_1 = col_1.colorize current_task_color
col_2 = col_2.colorize current_task_color
col_3 = col_3.colorize current_task_color
end
puts "| #{col_1} | #{col_2} | #{col_3} |"
end
end
# Displays commonly used text objects in the command line
#
# @param type the type of text object to display (:top/:bottom/:intro)
def display_object type
horizontal_border = "+-------+-#{'-'*@name_width}-+-#{'-'*@elapsed_time_width}-+"
case type
when :top
puts horizontal_border
if TrackingConfig[:show_header]
puts "| start | #{pad('task', @name_width, :center)} | #{pad('elapsed', @elapsed_time_width, :center)} |"
puts horizontal_border
end
when :bottom
puts horizontal_border
when :intro
intro_text = <<-EOF
You haven't started any tasks yet! :(
Run this to begin your first task:
tracking starting some work
EOF
intro_text.each_line do |line|
puts "| | #{pad(line.chomp, @name_width)} | #{pad(nil, @elapsed_time_width)} |"
end
end
end
# Pads tasks with whitespace to align them for display
#
# @param [String] string the string to pad
# @param [Integer] length the length of the resultant string
# @param [Symbol] align the alignment of the start string within the end
# string (:left/:right/:center)
# @return [String] the padded string
def pad(string, length, align=:left)
if string == nil
return ' ' * length
elsif string.length >= length
return string
else
difference = (length - string.length).to_f
case align
when :left
return string + ' ' * difference
when :right
return ' ' * difference + string
when :center
return ' '*(difference/2).floor + string + ' '*(difference/2).ceil
end
end
end
# Word wraps tasks into multiple lines for display (based on the user's task
# width setting)
#
# @param [String] task the task string to split up
# @return [Array] an array of strings, each containing an individual line of
# wrapped text
def split_task task
# If the task fits
if task.length <= TrackingConfig[:task_width]
return [task]
# If the task needs to be split
else
words = task.split(' ')
split = []
line = ''
words.each do |word|
# If the word needs to be split
if word.length > TrackingConfig[:task_width]
# Add the start of the word onto the first line (even if it has
# already started)
while line.length < TrackingConfig[:task_width]
line += word[0]
word = word[1..-1]
end
split << line
# Split the rest of the word up onto new lines
split_word = word.scan(%r[.{1,#{TrackingConfig[:task_width]}}])
split_word[0..-2].each do |word_section|
split << word_section
end
line = split_word.last
# If the word would fit on a new line
elsif (line + word).length > TrackingConfig[:task_width]
split << line.chomp
line = word
# If the word can be added to this line
else
line += word
end
# Add a space to the end of the last word, if it would fit
line += ' ' if line.length != TrackingConfig[:task_width]
end
split << line
return split
end
end
# Gets a string of text from ARGV. Lets the user use spaces in strings
# without typing quotes around his/her text. Also removes tab characters
# from input data, so they would not interfere with the CSV file (which is
# separated with the tab character).
#
# @return input text from ARGV
def text_from_args
return ARGV.join(' ').gsub("\t",'')
end
# Use option parser to parse command line arguments and run the selected
# command with its selected options
#
# @param [Array] args the command line arguments passed to OptionParser
# (this should only need to be overridden for testing)
def parse args=ARGV
OptionParser.new do |opts|
# Setup
version_path = File.expand_path('../../VERSION', File.dirname(__FILE__))
opts.version = File.exist?(version_path) ? File.read(version_path) : ''
# Start of help text
opts.banner = 'Usage: tracking [mode]'
opts.separator ' display recent tasks'
opts.separator ' <task description> start a new task with the given text (spaces allowed)'
# Modes
opts.on('-f', '--find', 'display all tasks that match a search query') do
display(:query => text_from_args)
return
end
opts.on('-a', '--all', 'display all tasks') do
display(:max => :all)
return
end
opts.on('-n', '--number integer', 'display n tasks') do |number_str|
display(:max => number_str.to_i)
return
end
opts.on('-r', '--rename', 'rename the last task') do
List.rename text_from_args
display
return
end
opts.on('-d', '--delete', 'delete the last task') do
List.delete
display
return
end
opts.on('-c', '--clear', 'delete all tasks') do
List.clear
puts 'List cleared.'
return
end
opts.on('-h', '--help', 'display this help information') do
puts opts
return
end
end.parse! args
# Basic modes (display and add)
if args.count == 0
# Display all tasks
display
else
# Start a new task
List.add args.join(' ').gsub("\t",'')
display
end
end
end
end