tempo-cli 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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