mite.cmd 0.1.10

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