tempo-cli 0.1.0

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.
Files changed (73) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +4 -0
  3. data/Gemfile.lock +56 -0
  4. data/README.md +326 -0
  5. data/Rakefile +65 -0
  6. data/bin/tempo +477 -0
  7. data/features/arrange.feature +43 -0
  8. data/features/checkout.feature +63 -0
  9. data/features/end.feature +65 -0
  10. data/features/project.feature +246 -0
  11. data/features/report.feature +62 -0
  12. data/features/start.feature +87 -0
  13. data/features/step_definitions/tempo_steps.rb +138 -0
  14. data/features/support/env.rb +26 -0
  15. data/features/tempo.feature +13 -0
  16. data/features/update.feature +69 -0
  17. data/lib/file_record/directory.rb +11 -0
  18. data/lib/file_record/directory_structure/tempo/README.txt +4 -0
  19. data/lib/file_record/directory_structure/tempo/tempo_projects.yaml +6 -0
  20. data/lib/file_record/record.rb +120 -0
  21. data/lib/tempo/controllers/arrange_controller.rb +52 -0
  22. data/lib/tempo/controllers/base.rb +117 -0
  23. data/lib/tempo/controllers/checkout_controller.rb +42 -0
  24. data/lib/tempo/controllers/end_controller.rb +42 -0
  25. data/lib/tempo/controllers/projects_controller.rb +107 -0
  26. data/lib/tempo/controllers/records_controller.rb +21 -0
  27. data/lib/tempo/controllers/report_controller.rb +55 -0
  28. data/lib/tempo/controllers/start_controller.rb +42 -0
  29. data/lib/tempo/controllers/update_controller.rb +78 -0
  30. data/lib/tempo/models/base.rb +176 -0
  31. data/lib/tempo/models/composite.rb +71 -0
  32. data/lib/tempo/models/log.rb +194 -0
  33. data/lib/tempo/models/project.rb +73 -0
  34. data/lib/tempo/models/time_record.rb +235 -0
  35. data/lib/tempo/version.rb +3 -0
  36. data/lib/tempo/views/arrange_view.rb +27 -0
  37. data/lib/tempo/views/base.rb +82 -0
  38. data/lib/tempo/views/formatters/base.rb +30 -0
  39. data/lib/tempo/views/formatters/screen.rb +86 -0
  40. data/lib/tempo/views/projects_view.rb +82 -0
  41. data/lib/tempo/views/report_view.rb +26 -0
  42. data/lib/tempo/views/reporter.rb +70 -0
  43. data/lib/tempo/views/time_record_view.rb +30 -0
  44. data/lib/tempo/views/view_records/base.rb +117 -0
  45. data/lib/tempo/views/view_records/composite.rb +40 -0
  46. data/lib/tempo/views/view_records/log.rb +28 -0
  47. data/lib/tempo/views/view_records/project.rb +32 -0
  48. data/lib/tempo/views/view_records/time_record.rb +48 -0
  49. data/lib/tempo.rb +26 -0
  50. data/lib/time_utilities.rb +30 -0
  51. data/tempo-cli.gemspec +26 -0
  52. data/test/lib/file_record/directory_test.rb +30 -0
  53. data/test/lib/file_record/record_test.rb +106 -0
  54. data/test/lib/tempo/controllers/base_controller_test.rb +60 -0
  55. data/test/lib/tempo/controllers/project_controller_test.rb +24 -0
  56. data/test/lib/tempo/models/base_test.rb +173 -0
  57. data/test/lib/tempo/models/composite_test.rb +76 -0
  58. data/test/lib/tempo/models/log_test.rb +171 -0
  59. data/test/lib/tempo/models/project_test.rb +105 -0
  60. data/test/lib/tempo/models/time_record_test.rb +212 -0
  61. data/test/lib/tempo/views/base_test.rb +31 -0
  62. data/test/lib/tempo/views/formatters/base_test.rb +13 -0
  63. data/test/lib/tempo/views/formatters/screen_test.rb +94 -0
  64. data/test/lib/tempo/views/reporter_test.rb +40 -0
  65. data/test/lib/tempo/views/view_records/base_test.rb +77 -0
  66. data/test/lib/tempo/views/view_records/composite_test.rb +57 -0
  67. data/test/lib/tempo/views/view_records/log_test.rb +28 -0
  68. data/test/lib/tempo/views/view_records/project_test.rb +0 -0
  69. data/test/lib/tempo/views/view_records/time_record_test.rb +0 -0
  70. data/test/support/factories.rb +177 -0
  71. data/test/support/helpers.rb +69 -0
  72. data/test/test_helper.rb +31 -0
  73. metadata +230 -0
@@ -0,0 +1,235 @@
1
+ module Tempo
2
+ module Model
3
+ class TimeRecord < Tempo::Model::Log
4
+ attr_accessor :project, :description
5
+ attr_reader :start_time, :end_time, :tags
6
+
7
+ class << self
8
+
9
+ def current
10
+ return @current if @current && @current.end_time == :running
11
+ @current = nil
12
+ end
13
+
14
+ def current=( instance )
15
+ if instance.class == self
16
+ @current = instance
17
+ else
18
+ raise ArgumentError
19
+ end
20
+ end
21
+ end
22
+
23
+ def initialize(options={})
24
+
25
+ # declare these first for model organization when sent to YAML
26
+ @project_title = nil
27
+ @description = options.fetch :description, ""
28
+ @start_time = nil
29
+
30
+ # verify both start time and end time before sending to super
31
+ options[:start_time] ||= Time.now
32
+ verify_start_time options[:start_time]
33
+ @end_time = options.fetch :end_time, :running
34
+ verify_end_time options[:start_time], @end_time
35
+ super options
36
+
37
+ project = options.fetch :project, Tempo::Model::Project.current
38
+ @project = project.kind_of?(Integer) ? project : project.id
39
+
40
+ @tags = []
41
+ tag options.fetch(:tags, [])
42
+
43
+ # close out the running time record
44
+ if running?
45
+ if not self.class.current
46
+ self.class.current = self
47
+ else
48
+
49
+ current = self.class.current
50
+
51
+ # more recent entries exist, need to close out immediately
52
+ if current.start_time > @start_time
53
+ if current.start_time.day > @start_time.day
54
+ out = self.class.end_of_day @start_time
55
+ @end_time = out
56
+ # TODO add a new record onto the next day
57
+ else
58
+ @end_time = current.start_time
59
+ end
60
+
61
+ # close out the last current record
62
+ else
63
+ self.class.close_current @start_time
64
+ self.class.current = self
65
+ end
66
+ end
67
+
68
+ # close out any earlier running timerecords
69
+ else
70
+ if self.class.current
71
+ if self.class.current.start_time < @start_time
72
+ self.class.close_current @start_time
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ def start_time= time
79
+ @start_time = time if verify_start_time time
80
+ end
81
+
82
+ def end_time= time
83
+ @end_time = time if verify_end_time self.start_time, time
84
+ end
85
+
86
+ def valid_start_time? time
87
+ begin
88
+ verify_start_time time
89
+ rescue ArgumentError => e
90
+ return false
91
+ end
92
+ true
93
+ end
94
+
95
+ def valid_end_time? time
96
+ begin
97
+ verify_end_time self.start_time, time
98
+ rescue ArgumentError => e
99
+ return false
100
+ end
101
+ true
102
+ end
103
+
104
+ def project_title
105
+ Project.find_by_id( @project ).title if @project
106
+ end
107
+
108
+ def duration
109
+ if @end_time.kind_of? Time
110
+ end_time = @end_time
111
+ else
112
+ end_time = Time.now()
113
+ end
114
+ end_time.to_i - @start_time.to_i
115
+ end
116
+
117
+ def running?
118
+ @end_time == :running
119
+ end
120
+
121
+ def freeze_dry
122
+ record = super
123
+ record[:project_title] = project_title
124
+ record
125
+ end
126
+
127
+ def tag( tags )
128
+ return unless tags and tags.kind_of? Array
129
+ tags.each do |tag|
130
+ tag.split.each {|t| @tags << t if ! @tags.include? t }
131
+ end
132
+ @tags.sort!
133
+ end
134
+
135
+ def untag( tags )
136
+ return unless tags and tags.kind_of? Array
137
+ tags.each do |tag|
138
+ tag.split.each {|t| @tags.delete t }
139
+ end
140
+ end
141
+
142
+ def to_s
143
+ "#{@start_time} - #{@end_time}, #{project_title}: #{@description}"
144
+ end
145
+
146
+ private
147
+
148
+ #close current at the end time, or on the last minute
149
+ # of the day if end time is another day
150
+ #
151
+ def self.close_current end_time
152
+ if end_time.day > current.start_time.day
153
+ out = end_of_day current.start_time
154
+ current.end_time = out
155
+ else
156
+ current.end_time = end_time
157
+ end
158
+ end
159
+
160
+ # check a time against all loaded instances, verify that it doesn't
161
+ # fall in the middle of any closed time records
162
+ #
163
+ def verify_start_time time
164
+
165
+ # Check that there are currently
166
+ # records on the day to iterate through
167
+ dsym = self.class.date_symbol time
168
+ return true if not self.class.days_index[dsym]
169
+
170
+ self.class.days_index[dsym].each do |record|
171
+
172
+ next if record.end_time == :running
173
+ next if record == self
174
+ if time < record.end_time
175
+ raise ArgumentError, "Time conflict with existing record" if time_in_record? time, record
176
+ end
177
+ end
178
+ true
179
+ end
180
+
181
+ def verify_end_time start_time, end_time
182
+
183
+ # TODO: a better check for :running conditions
184
+ return true if end_time == :running
185
+
186
+ raise ArgumentError, "End time must be greater than start time" if end_time < start_time
187
+
188
+ dsym = self.class.date_symbol end_time
189
+ start_dsym = self.class.date_symbol start_time
190
+ raise ArgumentError, "End time must be on the same day as start time: #{start_time} : #{end_time}" if dsym != start_dsym
191
+
192
+ # this is necessary if this is the first record
193
+ # for the day and self is not yet added to index
194
+ return if not self.class.days_index[dsym]
195
+
196
+ self.class.days_index[dsym].each do |record|
197
+ next if record == self
198
+ raise ArgumentError, "Time conflict with existing record:" if time_span_intersects_record? start_time, end_time, record
199
+ end
200
+ true
201
+ end
202
+
203
+ # this is used for both start time and end times,
204
+ # so it will return true if the time is :running
205
+ # or if it is exactly the record start or end time
206
+ # these conditions need to be checked separately
207
+ def time_in_record? time, record
208
+ return false if record.end_time == :running
209
+ time >= record.start_time && time <= record.end_time
210
+ end
211
+
212
+ # All true conditions should be used to raise errors.
213
+ # Returns false is when sharing a single end and start point (a valid state).
214
+ # It does not invalidate a time span earlier than the record with a :running end time,
215
+ # this condition must be accounted for separately.
216
+ # It assumes a valid start and end time.
217
+ def time_span_intersects_record? start_time, end_time, record
218
+ if record.end_time == :running
219
+ return true if start_time <= record.start_time && end_time > record.start_time
220
+ return false
221
+ end
222
+ return false if start_time >= record.end_time
223
+ return true if start_time >= record.start_time && start_time < record.end_time
224
+ return false if record.end_time == :running
225
+ return true if end_time > record.start_time
226
+ return false
227
+ end
228
+
229
+ # returns the last minute of the day
230
+ def self.end_of_day time
231
+ Time.new(time.year, time.month, time.day, 23, 59)
232
+ end
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,3 @@
1
+ module Tempo
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,27 @@
1
+ module Tempo
2
+ module Views
3
+ class << self
4
+
5
+ def arrange_parent_child parent, child
6
+ ViewRecords::Message.new "parent project:"
7
+ ViewRecords::Project.new parent
8
+ ViewRecords::Message.new "child project:"
9
+ ViewRecords::Project.new child
10
+ end
11
+
12
+ def arrange_root project
13
+ ViewRecords::Message.new "root project:"
14
+ ViewRecords::Project.new project
15
+ end
16
+
17
+ def arrange_already_root project
18
+ ViewRecords::Message.new "already a root project:"
19
+ ViewRecords::Project.new project
20
+ end
21
+
22
+ def arrange_parse_error
23
+ ViewRecords::Message.new "arrange requires a colon (:) in the arguments", category: :error
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,82 @@
1
+ module Tempo
2
+ module Views
3
+ class << self
4
+
5
+ # called in the pre block, pushes relavent options to the reporter
6
+ def initialize_view_options command, global_options, options
7
+ view_opts = {}
8
+ view_opts[:verbose] = global_options[:verbose]
9
+ view_opts[:id] = global_options[:id]
10
+ case command
11
+ when :project, :p
12
+ if global_options[:verbose]
13
+ view_opts[:id] = true
14
+ view_opts[:tags] = true
15
+ view_opts[:active] = true
16
+ view_opts[:depth] = true
17
+ else
18
+ if options[:list]
19
+ view_opts[:depth] = true
20
+ view_opts[:active] = true
21
+ end
22
+ view_opts[:tags] = options[:tag] || options[:untag] ? true : false
23
+ view_opts[:id] = global_options[:id] || options[:id] ? true : false
24
+ end
25
+ end
26
+ Tempo::Views::Reporter.add_options view_opts
27
+ end
28
+
29
+ # DEPRACATE- View is returned in post
30
+ #
31
+ # puts each line if output=true
32
+ # else returns an array of view lines
33
+ def return_view( view, options={} )
34
+ output = options.fetch( :output, true )
35
+
36
+ if output
37
+ if view.is_a? String
38
+ puts view
39
+ else
40
+ view.each { |line| puts line }
41
+ end
42
+ end
43
+ view
44
+ end
45
+
46
+ def options_report( command, global_options, options, args )
47
+ globals_list = "global options: "
48
+ global_options.each {|k,v| globals_list += "#{k} = #{v}, " if k.kind_of? String and k.length > 1 and !v.nil? }
49
+ ViewRecords::Message.new globals_list[0..-2], category: :debug
50
+
51
+ options_list = "command options: "
52
+ options.each {|k,v| options_list += "#{k} = #{v}, " if k.kind_of? String and k.length > 1 and !v.nil? }
53
+ ViewRecords::Message.new options_list[0..-2], category: :debug
54
+
55
+
56
+ ViewRecords::Message.new "command: #{command}", category: :debug
57
+ ViewRecords::Message.new "args: #{args}", category: :debug
58
+ end
59
+
60
+ def no_items( items, category=:info )
61
+ ViewRecords::Message.new "no #{items} exist", category: category
62
+ end
63
+
64
+ def no_match_error( items, request, plural=true )
65
+ match = plural ? "match" : "matches"
66
+ ViewRecords::Message.new "no #{items} #{match} the request: #{request}", category: :error
67
+ end
68
+
69
+ def already_exists_error( item, request )
70
+ ViewRecords::Message.new "#{item} '#{request}' already exists", category: :error
71
+ end
72
+
73
+ def checkout_assistance( options={} )
74
+ ViewRecords::Message.new "checkout command run with no arguments"
75
+ ViewRecords::Message.new "perhaps you meant one of these?"
76
+ ViewRecords::Message.new " tempo checkout --add <new project name>"
77
+ ViewRecords::Message.new " tempo checkout <existing project>"
78
+ ViewRecords::Message.new "run `tempo checkout --help` for more information"
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,30 @@
1
+ # Tempo View Formatters are triggered by the View Reporter.
2
+ # The View Reporter sends it's stored view messages to each
3
+ # of it's formatters. It calls the method format_records and
4
+ # passes in the view records. If the formatter has a class method
5
+ # that handles the type of block passed in, it will process
6
+ # that view record. These class methods take the name "<record type>_block"
7
+ # where record type can be any child class of ViewRecord::Base
8
+ # see the screen formatter for an example of processing blocks.
9
+
10
+ module Tempo
11
+ module Views
12
+ module Formatters
13
+
14
+ class Base
15
+
16
+ # Here we check if our class methods include a proc block to handle the particular
17
+ # record type. See View Records for all possible record types. See screen formatter
18
+ # for examples of proc blocks.
19
+ #
20
+ def format_records records, options={}
21
+ @options = options
22
+ records.each do |record|
23
+ class_block = "#{record.type}_block"
24
+ send( class_block, record ) if respond_to? class_block
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,86 @@
1
+ # Tempo View Formatters are triggered by the View Reporter.
2
+ # The View Reporter sends it's stored view messages to each
3
+ # of it's formatters. It calls the method process records and
4
+ # passes in the view records. If the formatter has a class method
5
+ # that handles the type of block passed in, it will process
6
+ # that view record. These class methods take the name "<record type>_block"
7
+ # where record type can be any child class of ViewRecord::Base
8
+ # see <TODO> for an example of proc blocks.
9
+
10
+ module Tempo
11
+ module Views
12
+ module Formatters
13
+
14
+ class Screen < Tempo::Views::Formatters::Base
15
+
16
+ def message_block record
17
+ record.format do |m|
18
+ case m.category
19
+ when :error
20
+ raise m.message
21
+ when :info, :debug
22
+ puts m.message
23
+ end
24
+ m.message
25
+ end
26
+ end
27
+
28
+ def duration_block record
29
+ record.format do |d|
30
+ puts "#{ d.hours.to_s }:#{ d.minutes.to_s.rjust(2, '0') }"
31
+ end
32
+ end
33
+
34
+ # spacer for project titles, active project marked with *
35
+ def active_indicator( project )
36
+ indicator = project.current ? "* " : " "
37
+ end
38
+
39
+ def tag_partial tags, title_length
40
+ max_length = ViewRecords::Project.max_title_length
41
+ max_length += ViewRecords::Project.max_depth * 2 if @options[:depth]
42
+ max_length += 6 if @options[:id]
43
+ max_length += 2 if @options[:active]
44
+ spacer = [0, max_length - title_length].max
45
+ view = " " + ( " " * spacer )
46
+ return view + "tags: none" if tags.length < 1
47
+
48
+ view += "tags: ["
49
+ tags.each { |t| view += "#{t}, "}
50
+ view[0..-3] + "]"
51
+ end
52
+
53
+ def id_partial id
54
+ @options[:id] ? "[#{id}] ".rjust(6, ' ') : ""
55
+ end
56
+
57
+ def project_block record
58
+
59
+ record.format do |r|
60
+ @options[:active] = @options.fetch( :active, false )
61
+ record = r.title
62
+
63
+ id = id_partial r.id
64
+ active = @options[:active] ? active_indicator( r ) : ""
65
+ depth = @options[:depth] ? " " * r.depth : ""
66
+ title = r.title
67
+ view = "#{id}#{active}#{depth}#{title}"
68
+ tags = @options[:tags] ? tag_partial( r.tags, view.length ) : ""
69
+ view += tags
70
+ puts view
71
+ end
72
+ end
73
+
74
+ def timerecord_block record
75
+ record.format do |r|
76
+ id = id_partial r.id
77
+ 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}"
80
+ puts view
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,82 @@
1
+ module Tempo
2
+ module Views
3
+ class << self
4
+
5
+ def project_view project, depth=0
6
+ ViewRecords::Project.new project, depth: depth
7
+ end
8
+
9
+ def projects_list_view projects=Tempo::Model::Project.index, parent=:root, depth=0
10
+ return no_items( "projects" ) if projects.empty?
11
+
12
+ Tempo::Model::Project.sort_by_title projects do |projects|
13
+ projects.each do |p|
14
+
15
+ if p.parent == parent
16
+ project_view p, depth
17
+
18
+ if not p.children.empty?
19
+ next_depth = depth + 1
20
+ next_parent = p.id
21
+ child_array = projects_list_view projects, next_parent, next_depth
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ # list of sorted projects, no hierarchy
29
+ def projects_flat_list_view projects=Tempo::Model::Project.index
30
+
31
+ Tempo::Model::Project.sort_by_title projects do |projects|
32
+ projects.each do |p|
33
+ project_view p
34
+ end
35
+ end
36
+ end
37
+
38
+ def project_added project
39
+ ViewRecords::Message.new "added project:"
40
+ project_view project
41
+ end
42
+
43
+ def project_deleted project
44
+ ViewRecords::Message.new "deleted project:"
45
+ project_view project
46
+ end
47
+
48
+ def project_checkout project
49
+ ViewRecords::Message.new "switched to project:"
50
+ project_view project
51
+ end
52
+
53
+ def project_already_current project
54
+ ViewRecords::Message.new "already on project:"
55
+ project_view project
56
+ end
57
+
58
+ def project_tags project
59
+ ViewRecords::Message.new "altered project tags:"
60
+ project_view project
61
+ end
62
+
63
+ def ambiguous_project( matches, command )
64
+
65
+ ViewRecords::Message.new "The following projects matched your search:"
66
+
67
+ Tempo::Views::Reporter.add_options active: true
68
+ projects_flat_list_view matches
69
+
70
+ ViewRecords::Message.new "please refine your search or use --exact to match args exactly"
71
+
72
+ ViewRecords::Message.new "cannot #{command} multiple projects", category: :error
73
+ end
74
+
75
+ def project_assistance
76
+ ViewRecords::Message.new "you need to set up a new project before running your command"
77
+ ViewRecords::Message.new "run`tempo project --help` for more information"
78
+ no_items "projects", :error
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,26 @@
1
+ module Tempo
2
+ module Views
3
+ class << self
4
+
5
+ def report_records_view options={}
6
+
7
+ projects = options.fetch( :projects, Tempo::Model::Project.index )
8
+ return no_items( "projects" ) if projects.empty?
9
+
10
+ time_records = options.fetch( :time_records, Tempo::Model::TimeRecord.days_index )
11
+ return no_items( "time records" ) if time_records.empty?
12
+
13
+ time_records.each do |d_id, days_record|
14
+
15
+ 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:")
18
+
19
+ days_record.each do |time_record|
20
+ time_record_view time_record
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,70 @@
1
+ # The Views Reporter is responsible for presenting all the views
2
+ # using the specified formatters. The Reporter is initialized in the GLI pre block,
3
+ # where all additional formats are added.
4
+ # The reporter executes the reports during the post block, displaying all views that
5
+ # have been added by the views.
6
+ #
7
+ # class instance variables:
8
+ #
9
+ # @@formats
10
+ # an array of formatters, which will be passed the view records on exit
11
+ # Reporter will always run the error formater first, to check for errors in
12
+ # the view reports, followed by all added formatters, and then finally the screen
13
+ # formatter. This allows additional formatters to add view records, which
14
+ # will be presented on screen.
15
+ #
16
+ # @@view_records
17
+ # add view_records
18
+
19
+ module Tempo
20
+ module Views
21
+
22
+ class Reporter
23
+ @@formats
24
+ @@view_records
25
+ @@options
26
+
27
+ class << self
28
+ attr_accessor :view_records
29
+
30
+ def add_format *formats
31
+ @@formats ||= []
32
+ formats.each {|format| @@formats << format}
33
+ end
34
+
35
+ def formats
36
+ @@formats ||= []
37
+ end
38
+
39
+ def options
40
+ @@options ||= {}
41
+ end
42
+
43
+ def add_options options
44
+ @@options ||= {}
45
+ @@options.merge! options
46
+ end
47
+
48
+ def add_view_record record
49
+ @@view_records ||= []
50
+
51
+ if /Views::ViewRecords/.match record.class.name
52
+ @@view_records << record
53
+ else
54
+ raise InvalidViewRecordError
55
+ end
56
+ end
57
+
58
+ def view_records
59
+ @@view_records ||= []
60
+ end
61
+
62
+ def report
63
+ # TODO send records to added formatters
64
+ screen_formatter = Formatters::Screen.new
65
+ screen_formatter.format_records view_records, options
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,30 @@
1
+ module Tempo
2
+ module Views
3
+ class << self
4
+
5
+ def time_record_view time_record
6
+ ViewRecords::TimeRecord.new time_record
7
+ end
8
+
9
+ def start_time_record_view time_record
10
+ ViewRecords::Message.new "time record started:"
11
+ time_record_view time_record
12
+ end
13
+
14
+ def end_time_record_view time_record
15
+ ViewRecords::Message.new "time record ended:"
16
+ time_record_view time_record
17
+ end
18
+
19
+ def update_time_record_view time_record
20
+ ViewRecords::Message.new "time record updated:"
21
+ time_record_view time_record
22
+ end
23
+
24
+ def delete_time_record_view time_record
25
+ ViewRecords::Message.new "time record deleted:"
26
+ time_record_view time_record
27
+ end
28
+ end
29
+ end
30
+ end