tempo-cli 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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