mite.cmd 0.1.10

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.
@@ -0,0 +1,48 @@
1
+ require 'shellwords'
2
+
3
+ module MiteCmd
4
+ class Autocomplete
5
+ include Shellwords
6
+
7
+ attr_accessor :completion_table
8
+ attr_reader :calling_script
9
+
10
+ def initialize(calling_script)
11
+ @calling_script = calling_script
12
+ end
13
+
14
+ def bash_line
15
+ ENV['COMP_LINE'].to_s
16
+ end
17
+
18
+ def argument_string
19
+ bash_line.sub(/^(.*)#{File.basename calling_script}\s*/, '').close_unmatched_quotes
20
+ end
21
+
22
+ def partial_argument_string
23
+ bash_line[0..cursor_position+1].sub(/^(.*)#{File.basename calling_script}\s*/, '').close_unmatched_quotes
24
+ end
25
+
26
+ def current_word
27
+ return nil if argument_string =~ /\s$/ && bash_line.length == cursor_position
28
+ shellwords(partial_argument_string).last
29
+ end
30
+
31
+ def current_argument_index
32
+ return args.size if argument_string =~ /\s$/ && bash_line.length == cursor_position
33
+ args.index(current_word) || 0
34
+ end
35
+
36
+ def cursor_position
37
+ ENV['COMP_POINT'].to_i
38
+ end
39
+
40
+ def args
41
+ shellwords(argument_string)
42
+ end
43
+
44
+ def suggestions
45
+ completion_table[current_argument_index] ? completion_table[current_argument_index].select {|s| s =~ /^#{current_word}/i} : []
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,53 @@
1
+ Mite.class_eval do
2
+ def self.account_url
3
+ host_format % [protocol, domain_format % account, (port.blank? ? '' : ":#{port}")]
4
+ end
5
+ end
6
+
7
+ Mite::TimeEntry.class_eval do
8
+ # I need the full class path, otherwise there will be LoadErrors
9
+ def service
10
+ @service ||= Mite::Service.find(service_id) unless service_id.blank?
11
+ end
12
+
13
+ # I need the full class path, otherwise there will be LoadErrors
14
+ def project
15
+ @project ||= Mite::Project.find(project_id) unless project_id.blank?
16
+ end
17
+
18
+ def inspect
19
+ output = []
20
+ output << formatted_time.colorize(tracking? ? :lightyellow : :lightred)
21
+ output << formatted_revenue.colorize(:lightgreen) if revenue
22
+ output << "\tdoing #{service.name}" if service
23
+ output << "\tfor #{project.name}" if project
24
+ output << "\n\t\t|_ #{note}" unless note.blank?
25
+ output.join(' ')
26
+ end
27
+
28
+ def formatted_revenue
29
+ revenue.nil? ? '' : "%.2f $" % (revenue / 100.0)
30
+ end
31
+
32
+ def formatted_time
33
+ minutes = tracking? ? tracker.minutes : self.minutes
34
+ if minutes > 59
35
+ h = minutes/60
36
+ m = minutes-h*60
37
+ "#{h}:%.2d" % m
38
+ else
39
+ "0:%.2d" % minutes
40
+ end
41
+ end
42
+
43
+ end
44
+
45
+ Mite::Tracker.class_eval do
46
+ def time_entry
47
+ @time_entry ||= Mite::TimeEntry.find(id)
48
+ end
49
+
50
+ def inspect
51
+ time_entry.inspect
52
+ end
53
+ end
@@ -0,0 +1,50 @@
1
+ String.class_eval do
2
+ def quote(q="\"")
3
+ "#{q}#{self}#{q}"
4
+ end
5
+
6
+ def quote_if_spaced(q="\"")
7
+ self =~ /\s/ ? self.quote : self
8
+ end
9
+
10
+ def close_unmatched_quotes(q="\"")
11
+ (self.scan(/"/).size % 2) != 0 ? self+q : self
12
+ end
13
+
14
+ BASH_COLOR = {
15
+ :black => 30,
16
+ :red => 31,
17
+ :green => 32,
18
+ :yellow => 33,
19
+ :blue => 34,
20
+ :magenta => 35,
21
+ :cyan => 36,
22
+ :white => 37,
23
+ :default => 39
24
+ }
25
+ BASH_EFFECT = {
26
+ :none => 0,
27
+ :bright => 1,
28
+ :underline => 4,
29
+ :blink => 5,
30
+ :exchange => 7,
31
+ :hidden => 8
32
+ }
33
+ def colorize(options={})
34
+ return '' if self == ''
35
+ options = {:color => options} if options.is_a?(Symbol)
36
+ options[:color] = :default unless options[:color]
37
+ if options[:color] != :default && options[:color].to_s =~ /^(light|bright)/
38
+ options[:color] = options[:color].to_s.sub(/^(light|bright)/, '').to_sym
39
+ options[:effect] = :bright
40
+ end
41
+ options[:background] = :default unless options[:background]
42
+ options[:effect] = :none unless options[:effect]
43
+
44
+ effect_code = "#{BASH_EFFECT[options[:effect]]}"
45
+ background_code = "#{BASH_COLOR[options[:background]]+10}m"
46
+ color_code = "#{BASH_COLOR[options[:color]]}"
47
+ reset_code = "\e[0m"
48
+ "\e[#{effect_code};#{color_code};#{background_code}#{self}#{reset_code}"
49
+ end
50
+ end
@@ -0,0 +1,75 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{mite.cmd}
5
+ s.version = "0.1.10"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Lukas Rieder"]
9
+ s.date = %q{2010-02-11}
10
+ s.default_executable = %q{mite}
11
+ s.description = %q{A simple command line interface for mite, a sleek time tracking webapp.}
12
+ s.email = %q{l.rieder@gmail.com}
13
+ s.executables = ["mite"]
14
+ s.extra_rdoc_files = [
15
+ "LICENSE",
16
+ "README.textile"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "LICENSE",
21
+ "README.textile",
22
+ "Rakefile",
23
+ "TODO",
24
+ "VERSION",
25
+ "bin/mite",
26
+ "lib/mite_cmd.rb",
27
+ "lib/mite_cmd/application.rb",
28
+ "lib/mite_cmd/autocomplete.rb",
29
+ "lib/mite_ext.rb",
30
+ "lib/string_ext.rb",
31
+ "mite.cmd.gemspec",
32
+ "spec/mite_cmd/application_spec.rb",
33
+ "spec/mite_cmd/autocomplete_spec.rb",
34
+ "spec/mite_cmd_spec.rb",
35
+ "spec/mite_ext_spec.rb",
36
+ "spec/spec_helper.rb",
37
+ "spec/string_ext_spec.rb",
38
+ "vendor/yolk-mite-rb-0.0.3/CHANGES.txt",
39
+ "vendor/yolk-mite-rb-0.0.3/LICENSE",
40
+ "vendor/yolk-mite-rb-0.0.3/README.textile",
41
+ "vendor/yolk-mite-rb-0.0.3/Rakefile",
42
+ "vendor/yolk-mite-rb-0.0.3/VERSION.yml",
43
+ "vendor/yolk-mite-rb-0.0.3/lib/mite-rb.rb",
44
+ "vendor/yolk-mite-rb-0.0.3/lib/mite/customer.rb",
45
+ "vendor/yolk-mite-rb-0.0.3/lib/mite/project.rb",
46
+ "vendor/yolk-mite-rb-0.0.3/lib/mite/service.rb",
47
+ "vendor/yolk-mite-rb-0.0.3/lib/mite/time_entry.rb",
48
+ "vendor/yolk-mite-rb-0.0.3/lib/mite/time_entry_group.rb",
49
+ "vendor/yolk-mite-rb-0.0.3/lib/mite/tracker.rb",
50
+ "vendor/yolk-mite-rb-0.0.3/lib/mite/user.rb"
51
+ ]
52
+ s.homepage = %q{http://github.com/Overbryd/mite.cmd}
53
+ s.rdoc_options = ["--charset=UTF-8"]
54
+ s.require_paths = ["lib"]
55
+ s.rubygems_version = %q{1.3.5}
56
+ s.summary = %q{A simple command line interface for basic mite tasks.}
57
+ s.test_files = [
58
+ "spec/mite_cmd/application_spec.rb",
59
+ "spec/mite_cmd/autocomplete_spec.rb",
60
+ "spec/mite_cmd_spec.rb",
61
+ "spec/mite_ext_spec.rb",
62
+ "spec/spec_helper.rb",
63
+ "spec/string_ext_spec.rb"
64
+ ]
65
+
66
+ if s.respond_to? :specification_version then
67
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
68
+ s.specification_version = 3
69
+
70
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
71
+ else
72
+ end
73
+ else
74
+ end
75
+ end
@@ -0,0 +1,532 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe MiteCmd::Application, 'new' do
4
+ it "should load the configuration for MiteCmd" do
5
+ MiteCmd.should_receive(:load_configuration)
6
+ MiteCmd::Application.new []
7
+ end
8
+
9
+ it "should not load the configuration if the first argument is 'configure'" do
10
+ MiteCmd.should_not_receive(:load_configuration)
11
+ MiteCmd::Application.new ['configure']
12
+ end
13
+
14
+ it "should set arguments" do
15
+ MiteCmd.stub!(:load_configuration)
16
+ MiteCmd::Application.new(['1', '2', '3']).instance_variable_get('@arguments').should == ['1', '2', '3']
17
+ end
18
+ end
19
+
20
+ describe MiteCmd::Application, 'run' do
21
+ before(:each) do
22
+ MiteCmd.stub!(:load_configuration)
23
+ end
24
+
25
+ describe 'no argument' do
26
+ before(:each) do
27
+ @application = MiteCmd::Application.new []
28
+ @application.stub!(:say)
29
+ @application.stub!(:flirt).and_return 'Your beautiful eyes touch my heart.'
30
+ end
31
+
32
+ it "should tell the inspection of the current tracker if there is one" do
33
+ tracker = stub('tracker', :inspect => 'I am the inspection of this tracker')
34
+ Mite::Tracker.stub!(:current).and_return tracker
35
+ @application.should_receive(:tell).with 'I am the inspection of this tracker'
36
+ @application.run
37
+ end
38
+
39
+ it "should tell something romantic if there is no current tracker" do
40
+ Mite::Tracker.stub!(:current).and_return nil
41
+ @application.should_receive(:tell).with 'Your beautiful eyes touch my heart.'
42
+ @application.run
43
+ end
44
+ end
45
+
46
+ describe 'the open argument' do
47
+ it "should try to open the account url or at least echo it" do
48
+ Mite.stub!(:account_url).and_return 'http://demo.mite.yo.lk'
49
+ application = MiteCmd::Application.new ['open']
50
+ application.should_receive(:exec).with "open 'http://demo.mite.yo.lk' || echo 'http://demo.mite.yo.lk'"
51
+ application.run
52
+ end
53
+ end
54
+
55
+ describe 'the help argument' do
56
+ it "should try to open the github repository of mighty mite or at least echo it" do
57
+ application = MiteCmd::Application.new ['help']
58
+ application.should_receive(:exec).with "open 'http://github.com/Overbryd/mite.cmd' || echo 'http://github.com/Overbryd/mite.cmd'"
59
+ application.run
60
+ end
61
+ end
62
+
63
+ describe 'the configure argument' do
64
+ before(:each) do
65
+ @application = MiteCmd::Application.new ['configure', 'demo', '123']
66
+ @application.stub!(:tell)
67
+ File.stub!(:chmod)
68
+ end
69
+
70
+ it "should generate a yaml formatted file in ~/.mite.yml with the account and the apikey" do
71
+ File.stub!(:expand_path).and_return '/tmp/.mite.yml'
72
+ File.should_receive(:open).with('/tmp/.mite.yml', 'w').and_yield :file_handle
73
+ YAML.should_receive(:dump).with({:account => 'demo', :apikey => '123'}, :file_handle)
74
+ @application.run
75
+ end
76
+
77
+ it "should tell something nice if the bash completion setup fails" do
78
+ @application.stub!(:try_to_setup_bash_completion).and_return false
79
+ @application.should_receive(:tell).with "Couldn't set up bash completion. I'm terribly frustrated. Maybe 'mite help' helps out."
80
+
81
+ File.stub!(:open) # prevents the yml file to be written
82
+ @application.run
83
+ end
84
+
85
+ it "should not tell something nice if bash completion is already set up or was ok" do
86
+ @application.stub!(:try_to_setup_bash_completion).and_return true
87
+ @application.should_not_receive(:tell)
88
+
89
+ File.stub!(:open) # prevents the yml file to be written
90
+ @application.run
91
+ end
92
+
93
+ it "should chmod the configuration file to 0600" do
94
+ File.stub!(:expand_path).and_return '/tmp/.mite.yml'
95
+ File.stub!(:file?).and_return true
96
+ File.should_receive(:chmod).with(0600, '/tmp/.mite.yml')
97
+ @application.run
98
+ end
99
+
100
+ describe 'and setup bash completion' do
101
+ it "should append the bash completion call for mite to ~/.bash_completion if it is a regular file and exists and return true" do
102
+ File.stub!(:expand_path).and_return '/tmp/.bash_completion'
103
+ File.should_receive(:file?).with('/tmp/.bash_completion').and_return true
104
+ file_handle = stub('file_handle')
105
+ File.should_receive(:open).with('/tmp/.bash_completion', 'a').and_yield file_handle
106
+ file_handle.should_receive(:puts).with("\n\ncomplete -C \"mite auto-complete\" mite")
107
+
108
+ File.stub!(:read).and_return ''
109
+ @application.send(:try_to_setup_bash_completion).should == true
110
+ end
111
+
112
+ it "should try '~/.bash_completion', '~/.bash_profile', '~/.bash_login', '~/.bashrc'" do
113
+ files = ['~/.bash_completion', '~/.bash_profile', '~/.bash_login', '~/.bashrc']
114
+ files_regexp = files.map {|f| Regexp.escape(f)}.join('|')
115
+ File.should_receive(:expand_path).with(
116
+ Regexp.new(files_regexp)
117
+ ).exactly(files.size).times
118
+
119
+ File.stub!(:file?).and_return false
120
+ @application.send :try_to_setup_bash_completion
121
+ end
122
+
123
+ it "should not open a file handle if the file does not exist" do
124
+ File.stub!(:file?).and_return false
125
+ File.should_not_receive(:open)
126
+ @application.send :try_to_setup_bash_completion
127
+ end
128
+
129
+ it "should not append the bash completion call twice" do
130
+ File.stub!(:read).and_return "\n\ncomplete -C \"mite auto-complete\" mite"
131
+ File.should_not_receive(:open)
132
+ @application.send :try_to_setup_bash_completion
133
+ end
134
+
135
+ it "should return false if the bash completion could not be set up" do
136
+ File.stub!(:file?).and_return false
137
+ @application.send(:try_to_setup_bash_completion).should == false
138
+ end
139
+ end
140
+
141
+ it "should raise an error if one of the last two arguments is missing" do
142
+ application = MiteCmd::Application.new ['configure', 'faildemo']
143
+ lambda {
144
+ application.run
145
+ }.should raise_error(MiteCmd::Exception)
146
+ end
147
+ end
148
+
149
+ describe 'the auto-complete argument' do
150
+ before(:each) do
151
+ @application = MiteCmd::Application.new ['auto-complete']
152
+ @application.stub!(:tell)
153
+
154
+ @autocomplete = stub('autocomplete', :completion_table => {}, :completion_table= => nil, :suggestions => [])
155
+ MiteCmd::Autocomplete.stub!(:new).and_return @autocomplete
156
+ end
157
+
158
+ it "should create a new instance of MiteCmd::Autocomplete setting the calling_script" do
159
+ MiteCmd.stub!(:calling_script).and_return '/usr/local/bin/mite'
160
+ MiteCmd::Autocomplete.should_receive(:new).with '/usr/local/bin/mite'
161
+ @application.run
162
+ end
163
+
164
+ it "should unmarshal the cached completion table from ~/.mite.cache if it exists" do
165
+ File.stub!(:exist?).and_return true
166
+ File.should_receive(:read).and_return :marshal_data
167
+ Marshal.should_receive(:load).and_return :cached_completion_table
168
+ @autocomplete.should_receive(:completion_table=).with :cached_completion_table
169
+ @autocomplete.stub!(:suggestions).and_return []
170
+ @application.run
171
+ end
172
+
173
+ it "should tell each suggestion from MiteCmd::Autocomplete" do
174
+ File.stub!(:exist?).and_return true
175
+ File.stub!(:read)
176
+ Marshal.stub!(:load)
177
+ @autocomplete.stub!(:suggestions).and_return ['Heinz', 'Peter']
178
+ @application.should_receive(:tell).with(/Heinz|Peter/).exactly(2).times
179
+ @application.run
180
+ end
181
+
182
+ it "should wrap suggestions inside quotes if they are spaced" do
183
+ File.stub!(:exist?).and_return true
184
+ File.stub!(:read)
185
+ Marshal.stub!(:load)
186
+ @autocomplete.stub!(:suggestions).and_return ['I need quotes', 'MeNot']
187
+ @application.should_receive(:tell).with(/"I need quotes"|MeNot/).exactly(2).times
188
+ @application.run
189
+ end
190
+
191
+ shared_examples_for 'an uncached completion table' do
192
+ before(:each) do
193
+ File.stub!(:exist?).and_return false
194
+
195
+ Mite::Project.stub!(:all).and_return [stub('project', :name => 'Demo Project')]
196
+ Mite::Service.stub!(:all).and_return [stub('service', :name => 'late night programming')]
197
+ Mite::TimeEntry.stub!(:all).and_return [stub('time entry', :note => 'shit 02:13 is really late')]
198
+
199
+ File.stub!(:open)
200
+ File.stub!(:chmod)
201
+ Marshal.stub!(:dump)
202
+
203
+ @completion_table = {
204
+ 0 => ['Demo Project'],
205
+ 1 => ['late night programming'],
206
+ 2 => ['"0:05"', '"0:05+"', '"0:15"', '"0:15+"', '"0:30"', '"0:30+"', '"1:00"', '"1:00+"'],
207
+ 3 => ['shit 02:13 is really late']
208
+ }
209
+ end
210
+
211
+ it "should create a new completion table" do
212
+ @application.send(:rebuild_completion_table).should == @completion_table
213
+ end
214
+
215
+ it "should save the new completion table to ~/.mite.cache" do
216
+ File.stub!(:expand_path).and_return '/tmp/.mite.cache'
217
+ File.should_receive(:open).with('/tmp/.mite.cache', 'w').and_yield :file_handle
218
+ Marshal.should_receive(:dump).with(@completion_table, :file_handle)
219
+ @application.run
220
+ end
221
+ end
222
+
223
+ describe 'and the completion table is not cached' do
224
+ it_should_behave_like 'an uncached completion table'
225
+ end
226
+
227
+ end
228
+
229
+ describe 'the rebuild-cache argument' do
230
+ before(:each) do
231
+ @application = MiteCmd::Application.new ['rebuild-cache']
232
+ @application.stub!(:tell)
233
+ File.stub!(:delete)
234
+ File.stub!(:chmod)
235
+ end
236
+
237
+ it "should delete the file at ~/.mite.cache if it exists" do
238
+ File.stub!(:exist?).and_return true
239
+ File.stub!(:expand_path).and_return '/tmp/.mite.cache'
240
+ File.should_receive(:delete).with '/tmp/.mite.cache'
241
+ @application.run
242
+ end
243
+
244
+ it "should not call delete on File if ~/.mite.cache does not exist" do
245
+ File.stub!(:exist?).and_return false
246
+ File.should_not_receive(:delete)
247
+ @application.run
248
+ end
249
+
250
+ it "should tell something nice if the cache has been rebuild" do
251
+ @application.should_receive(:tell).with 'The rebuilding of the cache has been done, Master. Your wish is my command.'
252
+ @application.run
253
+ end
254
+
255
+ it "should chmod the cache file to 0600" do
256
+ File.stub!(:expand_path).and_return '/tmp/.mite.cache'
257
+ File.stub!(:exist?).and_return true
258
+ File.should_receive(:chmod).with(0600, '/tmp/.mite.cache')
259
+ @application.run
260
+ end
261
+
262
+ it_should_behave_like 'an uncached completion table'
263
+ end
264
+
265
+ describe 'the simple report argument' do
266
+ shared_examples_for 'a simple report' do
267
+ before(:each) do
268
+ @application = MiteCmd::Application.new [@argument]
269
+ @application.stub!(:tell)
270
+
271
+ @time_entry = stub('time_entry', :inspect => 'I am a time entry.', :revenue => 1200, :minutes => 120)
272
+ @time_entry_with_nil_revenue = stub('time_entry_without_revenue', :inspect => 'I am a time entry.', :revenue => nil, :minutes => 18)
273
+ @time_entry_with_nil_revenue.stub!(:revenue).and_return nil
274
+ Mite::TimeEntry.stub!(:all).and_return [@time_entry, @time_entry, @time_entry_with_nil_revenue]
275
+ end
276
+
277
+ it "should tell an inspection of each time entry" do
278
+ Mite::TimeEntry.should_receive(:all).with(:params => hash_including(:at => @argument))
279
+ @time_entry.should_receive(:inspect).exactly(2).times
280
+ @time_entry_with_nil_revenue.should_receive(:inspect).at_least(:once)
281
+ @application.should_receive(:tell).with('I am a time entry.').exactly(3).times
282
+ @application.run
283
+ end
284
+
285
+ it "should only output the time entries of the current user" do
286
+ Mite::TimeEntry.should_receive(:all).with(:params => hash_including(:user_id => 'current'))
287
+ @application.run
288
+ end
289
+
290
+ it "should tell #{@argument}'s revenue, nicely formatted and colorized in lightgreen" do
291
+ @application.should_receive(:tell).with(/#{Regexp.escape "\e[1;32;49m24.00 $\e[0m"}/).at_least(:once)
292
+ @application.run
293
+ end
294
+
295
+ it "should tell #{@argument}'s total time, nicely formatted and colorized in red" do
296
+ @application.should_receive(:tell).with(/#{Regexp.escape "\e[1;31;49m4:18\e[0m"}/).at_least(:once)
297
+ @application.run
298
+ end
299
+ end
300
+
301
+ ['today', 'yesterday', 'this_week', 'last_week', 'this_month', 'last_month'].each do |argument|
302
+ describe argument do
303
+ before(:each) { @argument = argument }
304
+ it_should_behave_like 'a simple report'
305
+ end
306
+ end
307
+ end
308
+
309
+ describe 'the stop argument' do
310
+ before(:each) do
311
+ @application = MiteCmd::Application.new ['stop']
312
+ @application.stub!(:tell)
313
+
314
+ time_entry = stub('time_entry', :inspect => 'hey there.')
315
+ @current_tracker = stub('tracker', :time_entry => time_entry)
316
+ @current_tracker.stub!(:stop).and_return @current_tracker
317
+ Mite::Tracker.stub!(:current).and_return @current_tracker
318
+ end
319
+
320
+ it "should call stop on the current tracker" do
321
+ @current_tracker.should_receive :stop
322
+ @application.run
323
+ end
324
+
325
+ it "should do nothing if there is no current tracker" do
326
+ Mite::Tracker.stub!(:current).and_return nil
327
+ @application.run
328
+ end
329
+
330
+ it "should tell the inspection of the tracker's time entry if it has been stopped" do
331
+ @application.should_receive(:tell).with 'hey there.'
332
+ @application.run
333
+ end
334
+ end
335
+
336
+ describe 'the start argument' do
337
+ before(:each) do
338
+ @application = MiteCmd::Application.new ['start']
339
+ @application.stub!(:tell)
340
+
341
+ @time_entry = stub('time_entry', :start_tracker => nil, :inspect => 'I was started.')
342
+ Mite::TimeEntry.stub!(:first).and_return @time_entry
343
+ end
344
+
345
+ it "should call start_tracker on the last time entry of today" do
346
+ # last time entry by time = first by list order
347
+ Mite::TimeEntry.should_receive(:first).with(:params => {:at => 'today'})
348
+ @time_entry.should_receive(:start_tracker)
349
+ @application.run
350
+ end
351
+
352
+ it "should tell an inspection of the last time entry if it has been started" do
353
+ @application.should_receive(:tell).with 'I was started.'
354
+ @application.run
355
+ end
356
+
357
+ it "should tell something nice if there is no time entry to start" do
358
+ Mite::TimeEntry.stub!(:first).and_return nil
359
+ @application.should_receive(:tell).with "Oh my dear! I tried hard, but I could'nt find any time entry for today."
360
+ @application.run
361
+ end
362
+ end
363
+
364
+ end
365
+
366
+ describe MiteCmd::Application, 'dynamic time entry creation' do
367
+ before(:each) do
368
+ MiteCmd.stub!(:load_configuration)
369
+
370
+ @time_entry = stub('time_entry', :start_tracker => nil, :inspect => '')
371
+ Mite::TimeEntry.stub!(:create).and_return @time_entry
372
+ Mite::Project.stub! :first
373
+ Mite::Project.stub! :create
374
+ Mite::Service.stub! :first
375
+ Mite::Service.stub! :create
376
+ end
377
+
378
+ def new_application(args=[])
379
+ application = MiteCmd::Application.new args
380
+ application.stub!(:tell)
381
+ application
382
+ end
383
+
384
+ it "should tell an inspection of the time entry" do
385
+ @time_entry.stub!(:inspect).and_return 'My name is entry, time entry.'
386
+ application = new_application(['Project', 'Service', 'Note'])
387
+ application.should_receive(:tell).with 'My name is entry, time entry.'
388
+ application.run
389
+ end
390
+
391
+ describe 'the + argument' do
392
+ it "should create and start a new time entry" do
393
+ time_entry = stub('time_entry')
394
+ Mite::TimeEntry.should_receive(:create).with(:minutes => 0).and_return time_entry
395
+ time_entry.should_receive :start_tracker
396
+ new_application(['+']).run
397
+ end
398
+ end
399
+
400
+ describe 'with a time given' do
401
+ it "should parse minutes as integer out of h:mm(+)?" do
402
+ new_application.send(:parse_minutes, '1:18').should == 78
403
+ new_application.send(:parse_minutes, '72:00').should == 4320
404
+ new_application.send(:parse_minutes, '0:01+').should == 1
405
+ end
406
+
407
+ it "should parse minutes as integer out of h(:)?(+)?" do
408
+ new_application.send(:parse_minutes, '3').should == 180
409
+ new_application.send(:parse_minutes, '2:').should == 120
410
+ new_application.send(:parse_minutes, '1.5').should == 90
411
+ new_application.send(:parse_minutes, '2.5+').should == 150
412
+ new_application.send(:parse_minutes, '5:').should == 300
413
+ new_application.send(:parse_minutes, '1:+').should == 60
414
+ new_application.send(:parse_minutes, '0.5:').should == 30
415
+ end
416
+
417
+ it "should parse minutes as 0 out of +" do
418
+ new_application.send(:parse_minutes, '+').should == 0
419
+ end
420
+
421
+ it "should add the parsed minutes to the attributes" do
422
+ Mite::TimeEntry.should_receive(:create).with hash_including(:minutes => 78)
423
+ new_application(['ARG1', 'ARG2', '1:18', 'ARG4']).run
424
+ end
425
+
426
+ it "should start the tracker for the time entry if the attributes is suffixed with '+'" do
427
+ @time_entry.should_receive(:start_tracker)
428
+ new_application(['ARG1', 'ARG2', '1:18+', 'ARG4']).run
429
+ end
430
+ end
431
+
432
+ describe 'with a project name given' do
433
+ before(:each) do
434
+ @project = stub('project', :id => 1)
435
+ Mite::Project.stub!(:first).and_return @project
436
+ end
437
+
438
+ it "should try if the first argument is an existing project" do
439
+ Mite::Project.should_receive(:first).with(:params => {:name => 'Project No1'})
440
+ new_application(['Project No1']).run
441
+ end
442
+
443
+ it "should add the project id to the attributes if a project was found" do
444
+ Mite::TimeEntry.should_receive(:create).with hash_including(:project_id => 1)
445
+ new_application(['Project No1']).run
446
+ end
447
+
448
+ it "should create a new project unless a project was found" do
449
+ Mite::Project.stub!(:first).and_return nil
450
+ @project.stub!(:id).and_return 1234
451
+ Mite::Project.should_receive(:create).with(:name => 'I do not exist').and_return @project
452
+ new_application(['I do not exist']).run
453
+ end
454
+
455
+ it "should not create a new project if the first argument is a time argument" do
456
+ Mite::Project.stub!(:first).and_return nil
457
+ Mite::Project.should_not_receive(:create)
458
+ new_application(['1:11+']).run
459
+ end
460
+ end
461
+
462
+ describe "with a service name given" do
463
+ before(:each) do
464
+ @service = stub('service', :id => 2)
465
+ Mite::Service.stub!(:first).and_return @service
466
+ end
467
+
468
+ it "should try if the second argument is an existing service" do
469
+ Mite::Service.should_receive(:first).with(:params => {:name => 'Carwashing'})
470
+ new_application(['1', 'Carwashing']).run
471
+ end
472
+
473
+ it "should add the service id to the attributes if a service was found" do
474
+ Mite::TimeEntry.should_receive(:create).with hash_including(:service_id => 2)
475
+ new_application(['1', 'Read a book']).run
476
+ end
477
+
478
+ it "should create a new service unless a service was found" do
479
+ Mite::Service.stub!(:first).and_return nil
480
+ @service.stub!(:id).and_return 15
481
+ Mite::Service.should_receive(:create).with(:name => 'I do not exist yet').and_return @service
482
+ new_application(['1', 'I do not exist yet']).run
483
+ end
484
+
485
+ it "should not create a new service if the second argument is a time argument" do
486
+ Mite::Service.stub!(:first).and_return nil
487
+ Mite::Service.should_not_receive(:create)
488
+ new_application(['1', '0:18']).run
489
+ end
490
+
491
+ it "should do nothing on services if there is no second argument" do
492
+ application = new_application(['1'])
493
+ application.should_not_receive :find_or_create_service
494
+ application.run
495
+ end
496
+ end
497
+
498
+ describe "with a note given" do
499
+ it "should parse the note out of the arguments" do
500
+ new_application.send(:parse_note, ['0:12', 'gnarr gnarr'], '0:12').should == 'gnarr gnarr'
501
+ new_application.send(:parse_note, ['1', '1:30+', 'bla bla'], '1:30+').should == 'bla bla'
502
+ new_application.send(:parse_note, ['Project', 'Service', 'NOTE!'], nil).should == 'NOTE!'
503
+ end
504
+
505
+ it "should add the note to the attributes if the fourth argument is given" do
506
+ Mite::TimeEntry.should_receive(:create).with hash_including(:note => 'Reminder')
507
+ new_application(['ARG1', 'ARG2', '3+', 'Reminder']).run
508
+ end
509
+
510
+ it "should add the note to the attributes if it is third argument and no time is given" do
511
+ Mite::TimeEntry.should_receive(:create).with hash_including(:note => 'bla bla')
512
+ new_application(['ARG1', 'ARG2', 'bla bla']).run
513
+ end
514
+
515
+ it "should add the note to the attributes if a time argument is followed by a note" do
516
+ Mite::TimeEntry.should_receive(:create).with hash_including(:note => 'gnarr gnarr')
517
+ new_application(['1:02', 'gnarr gnarr']).run
518
+ Mite::TimeEntry.should_receive(:create).with hash_including(:note => 'glubb glubb')
519
+ new_application(['ARG1', '3:04', 'glubb glubb']).run
520
+ end
521
+ end
522
+ end
523
+
524
+ describe MiteCmd::Application, 'flirt' do
525
+ before(:each) do
526
+ MiteCmd.stub!(:load_configuration)
527
+ end
528
+
529
+ it "should return a random flirt as string" do
530
+ MiteCmd::Application.new.flirt.should be_kind_of(String)
531
+ end
532
+ end