tempo-cli 0.2.5 → 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6f1e31cb5d21c90343bf98498d67ece6b53532fe
4
- data.tar.gz: 22380af6098cebe55438b9878a5324f4024d414b
3
+ metadata.gz: 59fac2395c13e4a0a58701053e5ed9696146bb44
4
+ data.tar.gz: 42df9a9ed12b2a50771cd0b9e64500f346bacef7
5
5
  SHA512:
6
- metadata.gz: 4cd63f381d1b7267e0951a2aa3f709fffadc25db098c7581e929254c28fe36ca334c849243b79d22e407ea41c582f642d44ad4784617b01373ba5fb535e77507
7
- data.tar.gz: 64038cd234cecf689e3f48c20719de6d1c9843c87952570bb73ffb74c70eaea4a4548ba2532e813ff087d5ad5bbe1722e3f919dbb17ca8decfe2ea584b3b6dad
6
+ metadata.gz: 26a42ed193477f0dde232ef0209d70899d1fdc1b338e27779c5714d73f1f6b8499db8d1de0f0f81955783067f97ed2b533d336f61aafd3f11a016a18a3fa26d5
7
+ data.tar.gz: 77f7093821bd7a097f7cc7df90a7ea4426ff81acf10c776e3799af78a2ec6315744a82eb4e5ffedc7c556d76a32ea65c55bc64096afcfaaff8d5581e5db50d84
data/bin/tempo CHANGED
@@ -377,6 +377,11 @@ command [:report, :r] do |c|
377
377
  c.default_value 'today'
378
378
  c.flag [:t, :to]
379
379
 
380
+ c.desc 'order reports by date or project'
381
+ c.arg_name '[project, date]'
382
+ c.default_value 'date'
383
+ c.flag [:o, :order]
384
+
380
385
  c.action do |global_options, options, args|
381
386
  controller = Tempo::Controllers::Report
382
387
 
@@ -47,7 +47,7 @@ module Tempo
47
47
 
48
48
  return Views.no_items( "time records#{error_timeframe}", :error ) if @time_records.index.empty?
49
49
 
50
- Views.report_records_view
50
+ Views.report_records_view options
51
51
  end
52
52
  end #class << self
53
53
  end
@@ -43,7 +43,9 @@ module Tempo
43
43
  record = @time_records.find_by_id( options[:id], day )
44
44
  return Views.no_match_error( "time record on #{day.strftime('%m/%d/%Y')}", "id = #{options[:id]}", false ) if !record
45
45
  else
46
- record = @time_records.index.last
46
+ # TODO: this will still not pick the days last entry
47
+ # by time if ids are out of order and none are running.
48
+ record = @time_records.current || @time_records.index.last
47
49
  return Views.no_items( "time records on #{day.strftime('%m/%d/%Y')}", :error ) if ! record
48
50
  end
49
51
 
@@ -66,7 +68,6 @@ module Tempo
66
68
  if options[:start]
67
69
  start_time = Time.parse options[:start]
68
70
  return Views.no_match_error( "valid timeframe", options[:at], false ) if start_time.nil?
69
-
70
71
  if record.valid_start_time? start_time
71
72
  record.start_time = start_time
72
73
 
data/lib/tempo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Tempo
2
- VERSION = '0.2.5'
2
+ VERSION = '0.2.6'
3
3
  end
@@ -22,10 +22,15 @@ module Tempo
22
22
  view_opts[:tags] = options[:tag] || options[:untag] ? true : false
23
23
  view_opts[:id] = global_options[:id] || options[:id] ? true : false
24
24
  end
25
+ when :report, :r
26
+ if /^p(roject)?$/.match(options[:order]) && ! global_options[:verbose]
27
+ view_opts[:bullet_report] = true
28
+ end
25
29
  end
26
30
  Tempo::Views::Reporter.add_options view_opts
27
31
  end
28
32
 
33
+ # called in the preblock when verbose = true
29
34
  def options_report(command, global_options, options, args)
30
35
  globals_list = "global options: "
31
36
  global_options.each {|k,v| globals_list += "#{k} = #{v}, " if k.kind_of? String and k.length > 1 and !v.nil? }
@@ -13,16 +13,38 @@ module Tempo
13
13
 
14
14
  class Base
15
15
 
16
+ def initialize(options={})
17
+ @options = options
18
+ end
19
+
20
+ def report(record)
21
+ class_block = "#{record.type}_block"
22
+
23
+ # We handle containers separately
24
+ if /container/.match class_block
25
+ format_records_container(record)
26
+ else
27
+ send(class_block, record) if respond_to? class_block
28
+ end
29
+ end
30
+
16
31
  # Here we check if our class methods include a proc block to handle the particular
17
32
  # record type. See View Records for all possible record types. See screen formatter
18
33
  # for examples of proc blocks.
19
34
  #
20
- def format_records(records, options={})
21
- @options = options
35
+ def format_records(records)
22
36
  records.each do |record|
23
- class_block = "#{record.type}_block"
24
- send( class_block, record ) if respond_to? class_block
37
+ report record
38
+ end
39
+ end
40
+
41
+ # Records containers handle nested records
42
+ def format_records_container(container)
43
+ report container.pre if container.pre
44
+ container.records.each do |record|
45
+ report record
25
46
  end
47
+ report container.post if container.post
26
48
  end
27
49
  end
28
50
  end
@@ -31,6 +31,8 @@ module Tempo
31
31
  end
32
32
  end
33
33
 
34
+ # PARTIALS --------------------------------------------------------------------/
35
+
34
36
  # spacer for project titles, active project marked with *
35
37
  def active_indicator(project)
36
38
  indicator = project.current ? "* " : " "
@@ -54,6 +56,9 @@ module Tempo
54
56
  @options[:id] ? "[#{id}] ".rjust(6, ' ') : ""
55
57
  end
56
58
 
59
+ # PARTIALS --------------------------------------------------------------------/
60
+
61
+
57
62
  def project_block(record)
58
63
 
59
64
  record.format do |r|
@@ -72,11 +77,16 @@ module Tempo
72
77
  end
73
78
 
74
79
  def timerecord_block(record)
80
+ #require 'pry'; binding.pry
75
81
  record.format do |r|
76
82
  id = id_partial r.id
77
83
  running = r.running ? "*" : " "
78
- description = r.description.empty? ? "#{r.project}" : "#{r.project}: #{r.description}"
79
- view = "#{id}#{r.start_time.strftime('%H:%M')} - #{r.end_time.strftime('%H:%M')}#{running} [#{r.duration.format}] #{description}"
84
+ if @options[:bullet_report]
85
+ view = " * #{r.description}"
86
+ else
87
+ description = r.description.empty? ? "#{r.project}" : "#{r.project}: #{r.description}"
88
+ view = "#{id}#{r.start_time.strftime('%H:%M')} - #{r.end_time.strftime('%H:%M')}#{running} [#{r.duration.format}] #{description}"
89
+ end
80
90
  puts view
81
91
  end
82
92
  end
@@ -4,20 +4,59 @@ module Tempo
4
4
 
5
5
  def report_records_view(options={})
6
6
 
7
+ # Haven't we already checked for this?
7
8
  projects = options.fetch(:projects, Tempo::Model::Project.index)
8
9
  return no_items("projects") if projects.empty?
9
10
 
11
+ # TODO: Document here days index structure, give example of sending in subsets
12
+ # so the Records Controller can manage the organization of the records.
10
13
  time_records = options.fetch( :time_records, Tempo::Model::TimeRecord.days_index )
14
+
15
+ # It this going to break if 1 of X record containers is empty?
16
+ # Or should the controller not send empty records and this is good to check for?
17
+ # Maybe No time records for <date>?
11
18
  return no_items("time records") if time_records.empty?
12
19
 
20
+ if options[:order] == "date" || options[:order] == "d"
21
+ order_by_date time_records
22
+ elsif options[:order] == "project" || options[:order] == "p"
23
+ order_by_project projects, time_records
24
+ else
25
+ return error "Unable to report time records sorted by '#{options[:order]}'"
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def order_by_date(time_records)
13
32
  time_records.each do |d_id, days_record|
14
33
 
15
34
  day = Tempo::Model::TimeRecord.day_id_to_time d_id
16
- ViewRecords::Message.new ""
17
- ViewRecords::Message.new day.strftime("Records for %m/%d/%Y:")
35
+
36
+ container = ViewRecords::TimeRecordContainer.new
37
+ container.pre = ViewRecords::Message.new day.strftime("Records for %m/%d/%Y:\n\n"), postpone: true
18
38
 
19
39
  days_record.each do |time_record|
20
- time_record_view time_record
40
+ container.add(time_record_view time_record, postpone: true)
41
+ end
42
+ end
43
+ end
44
+
45
+ def order_by_project(projects, time_records)
46
+ project_records = Hash.new {|this_hash, nonexistent_key| this_hash[nonexistent_key] = [] }
47
+ time_records.each do |d_id, days_records|
48
+ days_records.each do |days_record|
49
+ # TODO: if project in projects (for filtering)
50
+ project_records[days_record.project] << days_record
51
+ end
52
+ end
53
+
54
+ project_records.each do |pr,time_records|
55
+ container = ViewRecords::TimeRecordContainer.new
56
+ #require 'pry'; binding.pry
57
+ container.pre = ViewRecords::Message.new "Records for #{Model::Project.find_by_id(pr).title}\n", postpone: true
58
+ time_records.each do |time_record|
59
+ container.add(time_record_view time_record, postpone: true)
21
60
  end
22
61
  end
23
62
  end
@@ -61,8 +61,8 @@ module Tempo
61
61
 
62
62
  def report
63
63
  # TODO send records to added formatters
64
- screen_formatter = Formatters::Screen.new
65
- screen_formatter.format_records view_records, options
64
+ screen_formatter = Formatters::Screen.new(options)
65
+ screen_formatter.format_records view_records
66
66
  end
67
67
  end
68
68
  end
@@ -2,8 +2,9 @@ module Tempo
2
2
  module Views
3
3
  class << self
4
4
 
5
- def time_record_view(time_record)
6
- ViewRecords::TimeRecord.new time_record
5
+ # Container sends postpone: true through options
6
+ def time_record_view(time_record, options={})
7
+ ViewRecords::TimeRecord.new time_record, options
7
8
  end
8
9
 
9
10
  def start_time_record_view(time_record)
@@ -27,6 +27,9 @@ module Tempo
27
27
  # category :error will raise an error after all viewRecords
28
28
  # have been run through the reporters
29
29
  #
30
+ # Containers send :pospone => true in options in order to manage
31
+ # triggering the message themselves
32
+ #
30
33
  class Message
31
34
  attr_accessor :type, :message, :category
32
35
 
@@ -34,7 +37,7 @@ module Tempo
34
37
  @message = message
35
38
  @category = options.fetch( :category, :info )
36
39
  @type = "message"
37
- Reporter.add_view_record self
40
+ Reporter.add_view_record self unless options[:postpone]
38
41
  end
39
42
 
40
43
  def format(&block)
@@ -93,6 +96,9 @@ module Tempo
93
96
  # tempo model without error, but most likely won't be as useful as a child class
94
97
  # taylored to the specifics of the actual model's child class.
95
98
  #
99
+ # Containers send :pospone => true in options in order to manage
100
+ # triggering the message themselves
101
+ #
96
102
  class Model
97
103
  attr_accessor :id, :type
98
104
 
@@ -101,7 +107,7 @@ module Tempo
101
107
 
102
108
  # example: Tempo::Model::Something => "something"
103
109
  @type = /Tempo::Model::(.*)$/.match( model.class.to_s )[1].downcase
104
- Reporter.add_view_record self
110
+ Reporter.add_view_record self unless options[:postpone]
105
111
  end
106
112
 
107
113
  def format(&block)
@@ -0,0 +1,68 @@
1
+ # Wrapper object for ViewRecords, able to collect multiple viewrecords
2
+ # this allows for a begining and end record, collecting total duration, etc.
3
+
4
+ # @pre = Optional record which is sent to the formatter before the collection
5
+ # @post = Optionsal record which is sent to the formatter after the collection
6
+ module Tempo
7
+ module Views
8
+ module ViewRecords
9
+
10
+ class Container
11
+ attr_accessor :type, :pre, :post
12
+
13
+ def initialize(options={})
14
+ # TODO: add error checking for pre and post, better handling nil values
15
+ @pre = options.fetch( :pre, nil )
16
+ @post = options.fetch( :post, nil )
17
+ @type = "container"
18
+ @collection = [] # handle records on init?
19
+ Reporter.add_view_record self unless options[:postpone]
20
+ end
21
+
22
+ # add a splat?
23
+ def add(record)
24
+ @collection << record
25
+ end
26
+
27
+ #TODO: Implement pre and post method with logic to handle both
28
+ # views reocrds and strings. See post in TimeRecordContainer
29
+ # for use case
30
+
31
+ def records
32
+ @collection
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+
40
+ module Tempo
41
+ module Views
42
+ module ViewRecords
43
+
44
+ # Handle a collection of time records
45
+ # Pre can hold the title of the collection (date, project, etc.)
46
+ # postreturns the total duration of all contained records
47
+ class TimeRecordContainer < ViewRecords::Container
48
+ attr_accessor :duration
49
+
50
+ def initialize(options={})
51
+ super options
52
+ @type = "time_record_container"
53
+ @duration = Duration.new
54
+ end
55
+
56
+ def add(record)
57
+ # TODO: fail if not a time record
58
+ super record
59
+ @duration.add record.duration.total
60
+ end
61
+
62
+ def post
63
+ ViewRecords::Message.new "Total: ------- [#{duration.format}] --------------------------------\n\n", postpone: true
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -7,20 +7,21 @@ describe Tempo do
7
7
 
8
8
  before do
9
9
  view_records_factory
10
- @formatter = Tempo::Views::Formatters::Screen.new
11
10
  end
12
11
 
13
12
  describe "Message View Records" do
14
13
 
15
14
  it "outputs the message" do
16
15
  records = [@message_1, @message_2]
17
- out = capture_stdout { @formatter.format_records records }
16
+ formatter = Tempo::Views::Formatters::Screen.new
17
+ out = capture_stdout { formatter.format_records records }
18
18
 
19
19
  assert_equal "All The Things I Did\non a busy busy day\n", out.string
20
20
  end
21
21
 
22
22
  it "raises an error when type error" do
23
- proc { @formatter.format_records [@error] }.must_raise RuntimeError
23
+ formatter = Tempo::Views::Formatters::Screen.new
24
+ proc { formatter.format_records [@error] }.must_raise RuntimeError
24
25
  end
25
26
  end
26
27
 
@@ -28,7 +29,8 @@ describe Tempo do
28
29
 
29
30
  it "outputs the duration" do
30
31
  records = [@duration]
31
- out = capture_stdout { @formatter.format_records records }
32
+ formatter = Tempo::Views::Formatters::Screen.new
33
+ out = capture_stdout { formatter.format_records records }
32
34
 
33
35
  assert_equal "2:40\n", out.string
34
36
  end
@@ -38,14 +40,16 @@ describe Tempo do
38
40
 
39
41
  it "outputs the project title" do
40
42
  records = [@project_1]
41
- out = capture_stdout { @formatter.format_records records }
43
+ formatter = Tempo::Views::Formatters::Screen.new
44
+ out = capture_stdout { formatter.format_records records }
42
45
 
43
46
  assert_equal "sheep herding\n", out.string
44
47
  end
45
48
 
46
49
  it "accepts option to include tags with spacing" do
47
50
  records = [@project_1, @project_2]
48
- out = capture_stdout { @formatter.format_records records, { tags: true }}
51
+ formatter = Tempo::Views::Formatters::Screen.new tags: true
52
+ out = capture_stdout { formatter.format_records records }
49
53
 
50
54
  assert_equal "sheep herding tags: none\n" +
51
55
  "horticulture - basement mushrooms tags: [farming, fungi]\n", out.string
@@ -53,7 +57,8 @@ describe Tempo do
53
57
 
54
58
  it "accepts option to include id" do
55
59
  records = [@project_2]
56
- out = capture_stdout { @formatter.format_records records, { id: true }}
60
+ formatter = Tempo::Views::Formatters::Screen.new id: true
61
+ out = capture_stdout { formatter.format_records records }
57
62
 
58
63
  assert_equal " [2] horticulture - basement mushrooms\n", out.string
59
64
  end
@@ -61,14 +66,16 @@ describe Tempo do
61
66
  it "accepts option to indent projects to proper depth" do
62
67
  @project_1.depth = 3
63
68
  records = [@project_1]
64
- out = capture_stdout { @formatter.format_records records, { depth: true }}
69
+ formatter = Tempo::Views::Formatters::Screen.new depth: true
70
+ out = capture_stdout { formatter.format_records records}
65
71
 
66
72
  assert_equal " sheep herding\n", out.string
67
73
  end
68
74
 
69
75
  it "indicates active project" do
70
76
  records = [@project_1, @project_2]
71
- out = capture_stdout { @formatter.format_records records, {active: true}}
77
+ formatter = Tempo::Views::Formatters::Screen.new active: true
78
+ out = capture_stdout { formatter.format_records records }
72
79
  assert_equal " sheep herding\n* horticulture - basement mushrooms\n", out.string
73
80
  end
74
81
  end
@@ -76,14 +83,16 @@ describe Tempo do
76
83
  describe "TimeRecord View Records" do
77
84
  it "has start/end time, duration, project and description" do
78
85
  records = [@time_record_1]
79
- out = capture_stdout { @formatter.format_records records }
86
+ formatter = Tempo::Views::Formatters::Screen.new
87
+ out = capture_stdout { formatter.format_records records }
80
88
 
81
89
  assert_equal "07:00 - 07:30 [0:30] sheep herding: day 1 pet the sheep\n", out.string
82
90
  end
83
91
 
84
92
  it "outputs a running indicator" do
85
93
  records = [@time_record_6]
86
- out = capture_stdout { @formatter.format_records records }
94
+ formatter = Tempo::Views::Formatters::Screen.new
95
+ out = capture_stdout { formatter.format_records records }
87
96
 
88
97
  assert_match /17:00 - \d{2}:\d{2}\*/, out.string
89
98
  end
@@ -26,6 +26,12 @@ describe Tempo do
26
26
  record = Tempo::Views::ViewRecords::Message.new "a message view record"
27
27
  Tempo::Views::Reporter.view_records.length.must_equal length_before + 1
28
28
  end
29
+
30
+ it "doesn't add itself with options postpone" do
31
+ length_before = Tempo::Views::Reporter.view_records.length
32
+ record = Tempo::Views::ViewRecords::Message.new "a message view record", postpone: true
33
+ Tempo::Views::Reporter.view_records.length.must_equal length_before
34
+ end
29
35
  end
30
36
 
31
37
  describe "Duration" do
@@ -74,4 +80,4 @@ describe Tempo do
74
80
  end
75
81
  end
76
82
  end
77
- end
83
+ end
@@ -0,0 +1,47 @@
1
+ require "test_helper"
2
+
3
+ describe Tempo do
4
+ describe "Views" do
5
+ describe "ViewRecords" do
6
+
7
+ describe "Container" do
8
+
9
+ it "has a type, pre,and post attribute" do
10
+ pre = Tempo::Views::ViewRecords::Message.new "pre message"
11
+ post = Tempo::Views::ViewRecords::Message.new "post message"
12
+ container = Tempo::Views::ViewRecords::Container.new pre: pre, post: post
13
+ container.type.must_equal "container"
14
+ container.pre.message.must_equal "pre message"
15
+ container.post.message.must_equal "post message"
16
+ end
17
+
18
+ it "has adds and contains a collection of view records" do
19
+ container = Tempo::Views::ViewRecords::Container.new
20
+ m1 = Tempo::Views::ViewRecords::Message.new "first message", postpone: true
21
+ m2 = Tempo::Views::ViewRecords::Message.new "second message", postpone: true
22
+ m3 = Tempo::Views::ViewRecords::Message.new "third message", postpone: true
23
+ [m1, m2, m3].each { |m| container.add m }
24
+ container.records.must_equal [m1,m2,m3]
25
+ end
26
+
27
+ it "adds itself to the Reporter" do
28
+ length_before = Tempo::Views::Reporter.view_records.length
29
+ record = Tempo::Views::ViewRecords::Container.new
30
+ Tempo::Views::Reporter.view_records.length.must_equal length_before + 1
31
+ end
32
+
33
+ it "doesn't add itself with options postpone" do
34
+ length_before = Tempo::Views::Reporter.view_records.length
35
+ record = Tempo::Views::ViewRecords::Message.new "a message view record", postpone: true
36
+ Tempo::Views::Reporter.view_records.length.must_equal length_before
37
+ end
38
+ end
39
+
40
+ describe "TimeRecordContainer" do
41
+ # TODO: Write ViewRecord Time Record tests first
42
+ # then add time records to a time records container
43
+ # test the total time is accurate
44
+ end
45
+ end
46
+ end
47
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tempo-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 0.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Gabel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-09 00:00:00.000000000 Z
11
+ date: 2014-09-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -165,6 +165,7 @@ files:
165
165
  - lib/tempo/views/time_record_view.rb
166
166
  - lib/tempo/views/view_records/base.rb
167
167
  - lib/tempo/views/view_records/composite.rb
168
+ - lib/tempo/views/view_records/container.rb
168
169
  - lib/tempo/views/view_records/log.rb
169
170
  - lib/tempo/views/view_records/project.rb
170
171
  - lib/tempo/views/view_records/time_record.rb
@@ -186,6 +187,7 @@ files:
186
187
  - test/lib/tempo/views/reporter_test.rb
187
188
  - test/lib/tempo/views/view_records/base_test.rb
188
189
  - test/lib/tempo/views/view_records/composite_test.rb
190
+ - test/lib/tempo/views/view_records/container_test.rb
189
191
  - test/lib/tempo/views/view_records/log_test.rb
190
192
  - test/lib/tempo/views/view_records/project_test.rb
191
193
  - test/lib/tempo/views/view_records/time_record_test.rb