tracking 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,29 +1,35 @@
1
- #utilities for tracking's config file
1
+ require 'fileutils'
2
+ require 'yaml'
2
3
 
3
- #imports
4
- require "yaml"
5
-
6
- #config module methods
7
4
  module Tracking
5
+ # Contains methods to interface with tracking's config file.
6
+ #
8
7
  # similar to Sam Goldstein's config.rb for timetrap
9
8
  # @see https://github.com/samg/timetrap/
10
9
  module Config
11
-
12
10
  extend self
13
11
 
14
- PATH = File.join(ENV["HOME"], ".tracking", "config.yml")
15
-
16
- #default values
12
+ # The path to the config file
13
+ PATH = File.join(ENV['HOME'], '.tracking', 'config.yml')
14
+
15
+ # The path to the config file's parent directory
16
+ DIR = File.join(ENV['HOME'], '.tracking')
17
+
18
+ # Default config hash
19
+ #
20
+ # @return [Hash<Symbol,Object>] the default config in a hash
17
21
  def defaults
18
22
  {
19
23
  # path to the data file (string, ~ can be used)
20
- :data_file => "~/.tracking/data.csv",
24
+ :data_file => '~/.tracking/data.csv',
21
25
  # number of lines to be displayed at once by default (integer)
22
26
  :lines => 10,
23
27
  # width of the task name column, in characters (integer)
24
28
  :task_width => 40,
25
29
  # format to use for elapsed time display (:colons or :letters)
26
30
  :elapsed_format => :colons,
31
+ # toggle colored display of the current (last) task
32
+ :color_current_task => true,
27
33
  # toggle header describing tracking's display columns (true or false)
28
34
  :show_header => true,
29
35
  # toggle display of seconds in elapsed time (true of false)
@@ -31,30 +37,43 @@ module Tracking
31
37
  }
32
38
  end
33
39
 
34
- #accessor for values in the config
40
+ # Overloading [] operator
41
+ #
42
+ # Accessor for values in the config
43
+ #
44
+ # @param [Symbol] key the key in the config hash
45
+ # @return [Object] the value associated with that key
35
46
  def [] key
47
+ write unless File.exist? PATH
36
48
  data = YAML.load_file PATH
37
49
  defaults.merge(data)[key]
38
50
  end
39
51
 
40
- #setter for keys in config
52
+ # Overloading []= operator
53
+ #
54
+ # Setter for values in the config
55
+ #
56
+ # @param [Symbol] key the key you are setting a value for
57
+ # @param [Object] value the value you associated with the key
41
58
  def []= key, value
59
+ write unless File.exist? PATH
42
60
  data = YAML.load_file PATH
43
61
  configs = defaults.merge(data)
44
62
  configs[key] = value
45
- File.open(PATH, "w") do |fh|
63
+ File.open(PATH, 'w') do |fh|
46
64
  fh.puts(configs.to_yaml)
47
65
  end
48
66
  end
49
67
 
50
- #writes the config file path
68
+ # Writes the configs to the file config.yml
51
69
  def write
52
70
  configs = if File.exist? PATH
53
71
  defaults.merge(YAML.load_file PATH)
54
72
  else
55
73
  defaults
56
74
  end
57
- File.open(PATH, "w") do |fh|
75
+ FileUtils.mkdir DIR unless File.directory? DIR
76
+ File.open(PATH, 'w') do |fh|
58
77
  fh.puts configs.to_yaml
59
78
  end
60
79
  end
@@ -1,76 +1,96 @@
1
- #tracking's core
1
+ require 'fileutils'
2
+ require 'yaml'
3
+ require 'time'
4
+ require 'csv'
2
5
 
3
- #imports
4
- require "yaml"
5
- require "time"
6
- require "csv"
7
-
8
- #model/controller module methods
9
6
  module Tracking
7
+ # Tracking's core. Contains methods for manipulating tasks in the data file
8
+ # and preparing its data for a user interface.
10
9
  module List
11
-
12
10
  extend self
13
11
 
14
- Config = YAML.load_file(ENV["HOME"] + "/.tracking/config.yml")
15
- $data_file = File.expand_path(Config[:data_file])
16
- $csv_options = { :col_sep => "\t" }
12
+ # The path to tracking's data file
13
+ @data_file = File.expand_path(Config[:data_file])
17
14
 
18
- #adds an item to the list
19
- def add item
20
- date = Time.now.to_s
21
- File.open($data_file, "a") do |file|
22
- file << [ date, item ].to_csv($csv_options)
23
- end
24
- end
15
+ # The options tracking uses for Ruby's CSV interface
16
+ @csv_options = { :col_sep => "\t" }
25
17
 
26
- #deletes an item from the list
27
- def delete
28
- lines = File.readlines $data_file
29
- lines.pop #or delete specific lines in the future
30
- File.open($data_file, "w") do |file|
31
- lines.each do |line|
32
- file << line
18
+ # Reads part of the data file and creates Task objects from that data
19
+ #
20
+ # @param [Integer] max the maximum number of items to get from the end of
21
+ # the data file
22
+ #
23
+ # @return [Array] an array of Task objects
24
+ def get max=Config[:lines]
25
+ if File.exist? @data_file
26
+ all_lines = CSV.read(@data_file, @csv_options)
27
+ lines = all_lines[-max..-1]
28
+ lines = all_lines if lines.nil?
29
+
30
+ tasks = []
31
+ lines.each_with_index do |line, i|
32
+ tasks << create_task_from_data(line, lines[i+1])
33
33
  end
34
+ return tasks
35
+ else
36
+ return []
34
37
  end
35
38
  end
36
39
 
37
- #clears the entire list
38
- def clear
39
- FileUtils.rm $data_file
40
- FileUtils.touch $data_file
40
+ # Generates a Task object from one or two lines of semi-parsed CSV data
41
+ #
42
+ # @param [Array] line the line of semi-parsed CSV data to use
43
+ # @param [Array] next_line the next line of data, if it exists
44
+ #
45
+ # @return [Task] the generated Task object
46
+ def create_task_from_data(line, next_line=nil)
47
+ name = line[1]
48
+ start_time = Time.parse line[0]
49
+ end_time = next_line.nil? ? Time.now : Time.parse(next_line[0])
50
+
51
+ return Task.new(name, start_time, end_time)
41
52
  end
42
53
 
43
- #gets and formats the amount of time passed between two times
44
- def get_elapsed_time(time1, time2)
45
- #calculate the elapsed time and break it down into different units
46
- seconds = (time2 - time1).floor
47
- minutes = hours = days = 0
48
- if seconds >= 60
49
- minutes = seconds / 60
50
- seconds = seconds % 60
51
- if minutes >= 60
52
- hours = minutes / 60
53
- minutes = minutes % 60
54
- if hours >= 24
55
- days = hours / 24
56
- hours = hours % 24
54
+ # Adds a task to the list
55
+ #
56
+ # @param [String] name the name of the task to add to the list
57
+ def add name, time=Time.now
58
+ FileUtils.touch @data_file unless File.exist? @data_file
59
+ File.open(@data_file, 'a') do |file|
60
+ file << [ time.to_s, name ].to_csv(@csv_options)
61
+ end
62
+ end
63
+
64
+ # Deletes the last task from the list
65
+ def delete
66
+ if File.exist? @data_file
67
+ lines = File.readlines @data_file
68
+ lines.pop # Or delete specific lines in the future
69
+ File.open(@data_file, 'w') do |file|
70
+ lines.each do |line|
71
+ file << line
57
72
  end
58
73
  end
59
74
  end
60
- #return a string of the formatted elapsed time
61
- case Config[:elapsed_format]
62
- when :colons
63
- if Config[:show_elapsed_seconds]
64
- return "%02d:%02d:%02d:%02d" % [days, hours, minutes, seconds]
65
- else
66
- return "%02d:%02d:%02d" % [days, hours, minutes]
67
- end
68
- when :letters
69
- if Config[:show_elapsed_seconds]
70
- return "%02dd %02dh %02dm %02ds" % [days, hours, minutes, seconds]
71
- else
72
- return "%02dd %02dh %02dm" % [days, hours, minutes]
73
- end
75
+ end
76
+
77
+ # Renames the last task in the list
78
+ #
79
+ # @param [String] name the new name for the last task
80
+ def rename name
81
+ # get task data
82
+ old_task = get(1).first
83
+ # delete last task
84
+ delete
85
+ # add new task with old time
86
+ add(name, old_task.raw(:start_time))
87
+ end
88
+
89
+ # Clears the entire list
90
+ def clear
91
+ if File.exist? @data_file
92
+ FileUtils.rm @data_file
93
+ FileUtils.touch @data_file
74
94
  end
75
95
  end
76
96
 
@@ -0,0 +1,99 @@
1
+ module Tracking
2
+ # The class for all Task objects created by List and passed to interfaces.
3
+ # Holds all relevant data for Tasks, as well as methods for computing new Task
4
+ # data as needed. Tasks internally save data (in their appropriate types) to
5
+ # intance variables, while public accessors return data in strings for
6
+ # display.
7
+ class Task
8
+
9
+ # Creates a new Task object. Data passed into its arguments is kept
10
+ # (unchanged) in instance variables.
11
+ #
12
+ # @param [String] name the tasks's name
13
+ # @param [Time] start_time the tasks's start time
14
+ # @param [Time] end_time the tasks's end time
15
+ def initialize(name, start_time, end_time)
16
+ @name = name
17
+ @start_time = start_time
18
+ @end_time = end_time
19
+ end
20
+
21
+ # Gets raw data from the task object, without doing any conversions or
22
+ # formatting
23
+ #
24
+ # @param [Symbol] key the key of the desired value
25
+ #
26
+ # @return the value of the requested key
27
+ def raw key
28
+ case key
29
+ when :name
30
+ return @name
31
+ when :start_time
32
+ return @start_time
33
+ when :end_time
34
+ return @end_time
35
+ end
36
+ end
37
+
38
+ # Converts the task object into a string (for debugging)
39
+ def to_s
40
+ return "name: #{name}; start: #{@start_time}; end: #{@end_time};"
41
+ end
42
+
43
+ # Calculates the length of strings from Task#elapsed_time (using the current
44
+ # elapsed time format).
45
+ #
46
+ # @return [String] the length of strings from Task#elapsed_time
47
+ def self.elapsed_time_length
48
+ test_task = Task.new('test', Time.now, Time.now)
49
+ return test_task.elapsed_time.length
50
+ end
51
+
52
+ # Accessor for this tasks's name (read/write)
53
+ attr_accessor :name
54
+
55
+ # Formats and returns the start time of this task.
56
+ #
57
+ # @return [String] the formatted start time of this task
58
+ def start_time
59
+ return @start_time.strftime('%H:%M')
60
+ end
61
+
62
+ # Calculates, formats, and returns the elapsed time of this task.
63
+ #
64
+ # @return [String] the formatted elapsed time of this task
65
+ def elapsed_time
66
+ # Calculate the elapsed time and break it down into different units
67
+ seconds = (@end_time - @start_time).floor
68
+ minutes = hours = days = 0
69
+ if seconds >= 60
70
+ minutes = seconds / 60
71
+ seconds = seconds % 60
72
+ if minutes >= 60
73
+ hours = minutes / 60
74
+ minutes = minutes % 60
75
+ if hours >= 24
76
+ days = hours / 24
77
+ hours = hours % 24
78
+ end
79
+ end
80
+ end
81
+ # Return a string of the formatted elapsed time
82
+ case Config[:elapsed_format]
83
+ when :colons
84
+ if Config[:show_elapsed_seconds]
85
+ return '%02d:%02d:%02d:%02d' % [days, hours, minutes, seconds]
86
+ else
87
+ return '%02d:%02d:%02d' % [days, hours, minutes]
88
+ end
89
+ when :letters
90
+ if Config[:show_elapsed_seconds]
91
+ return '%02dd %02dh %02dm %02ds' % [days, hours, minutes, seconds]
92
+ else
93
+ return '%02dd %02dh %02dm' % [days, hours, minutes]
94
+ end
95
+ end
96
+ end
97
+
98
+ end
99
+ end
@@ -0,0 +1,36 @@
1
+ require 'helper'
2
+ require 'fileutils'
3
+
4
+ describe Tracking::CLI do
5
+
6
+ before :each do
7
+ FileUtils.cd File.expand_path('~/.tracking')
8
+ FileUtils.mkdir 'test_backup'
9
+ %w(config.yml data.csv).each do |f|
10
+ FileUtils.mv(f, 'test_backup') if File.exist? f
11
+ end
12
+ end
13
+
14
+ after :each do
15
+ FileUtils.cd File.expand_path('~/.tracking/test_backup')
16
+ %w(config.yml data.csv).each do |f|
17
+ FileUtils.mv(f, File.expand_path('..')) if File.exist? f
18
+ end
19
+ FileUtils.cd File.expand_path('..')
20
+ FileUtils.rmdir 'test_backup'
21
+ end
22
+
23
+ it 'performs a few operations on a new list and then clears it' do
24
+ capture_output do
25
+ Tracking::List.clear
26
+ Tracking::CLI.display
27
+ Tracking::List.add 'first task'
28
+ Tracking::List.add 'second task'
29
+ Tracking::List.rename 'second task, renamed'
30
+ Tracking::CLI.display
31
+ Tracking::List.delete
32
+ Tracking::List.clear
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,21 @@
1
+ require 'bundler'
2
+ begin
3
+ Bundler.setup(:default, :development)
4
+ rescue Bundler::BundlerError => e
5
+ $stderr.puts e.message
6
+ $stderr.puts 'Run `bundle install` to install missing gems'
7
+ exit e.status_code
8
+ end
9
+
10
+ require_relative '../lib/tracking'
11
+
12
+ def capture_output &block
13
+ original_stdout = $stdout
14
+ $stdout = fake = StringIO.new
15
+ begin
16
+ yield
17
+ ensure
18
+ $stdout = original_stdout
19
+ end
20
+ fake.string
21
+ end
@@ -0,0 +1,81 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "tracking"
8
+ s.version = "1.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Nicolas McCurdy"]
12
+ s.date = "2012-09-10"
13
+ s.description = "See README for more information."
14
+ s.email = "thenickperson@gmail.com"
15
+ s.executables = ["tracking"]
16
+ s.extra_rdoc_files = [
17
+ "LICENSE.txt",
18
+ "README.md"
19
+ ]
20
+ s.files = [
21
+ ".document",
22
+ ".travis.yml",
23
+ "CHANGELOG.md",
24
+ "Gemfile",
25
+ "LICENSE.txt",
26
+ "README.md",
27
+ "Rakefile",
28
+ "VERSION",
29
+ "bin/tracking",
30
+ "lib/tracking.rb",
31
+ "lib/tracking/cli.rb",
32
+ "lib/tracking/config.rb",
33
+ "lib/tracking/list.rb",
34
+ "lib/tracking/task.rb",
35
+ "spec/cli_spec.rb",
36
+ "spec/helper.rb",
37
+ "tracking.gemspec"
38
+ ]
39
+ s.homepage = "http://github.com/thenickperson/tracking"
40
+ s.licenses = ["MIT"]
41
+ s.require_paths = ["lib"]
42
+ s.rubygems_version = "1.8.24"
43
+ s.summary = "A simple and configurable command line time tracker."
44
+
45
+ if s.respond_to? :specification_version then
46
+ s.specification_version = 3
47
+
48
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
49
+ s.add_runtime_dependency(%q<colorize>, ["~> 0.5"])
50
+ s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
51
+ s.add_development_dependency(%q<bundler>, [">= 1.0.0"])
52
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
53
+ s.add_development_dependency(%q<simplecov>, [">= 0"])
54
+ s.add_development_dependency(%q<yard>, [">= 0"])
55
+ s.add_development_dependency(%q<rspec>, [">= 0"])
56
+ s.add_development_dependency(%q<redcarpet>, [">= 0"])
57
+ s.add_development_dependency(%q<kramdown>, [">= 0"])
58
+ else
59
+ s.add_dependency(%q<colorize>, ["~> 0.5"])
60
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
61
+ s.add_dependency(%q<bundler>, [">= 1.0.0"])
62
+ s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
63
+ s.add_dependency(%q<simplecov>, [">= 0"])
64
+ s.add_dependency(%q<yard>, [">= 0"])
65
+ s.add_dependency(%q<rspec>, [">= 0"])
66
+ s.add_dependency(%q<redcarpet>, [">= 0"])
67
+ s.add_dependency(%q<kramdown>, [">= 0"])
68
+ end
69
+ else
70
+ s.add_dependency(%q<colorize>, ["~> 0.5"])
71
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
72
+ s.add_dependency(%q<bundler>, [">= 1.0.0"])
73
+ s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
74
+ s.add_dependency(%q<simplecov>, [">= 0"])
75
+ s.add_dependency(%q<yard>, [">= 0"])
76
+ s.add_dependency(%q<rspec>, [">= 0"])
77
+ s.add_dependency(%q<redcarpet>, [">= 0"])
78
+ s.add_dependency(%q<kramdown>, [">= 0"])
79
+ end
80
+ end
81
+