thenickperson/tracking

View on GitHub
lib/tracking/cli.rb

Summary

Maintainability
C
7 hrs
Test Coverage
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