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.
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.textile +162 -0
- data/Rakefile +33 -0
- data/TODO +4 -0
- data/VERSION +1 -0
- data/bin/mite +13 -0
- data/lib/mite_cmd.rb +34 -0
- data/lib/mite_cmd/application.rb +180 -0
- data/lib/mite_cmd/autocomplete.rb +48 -0
- data/lib/mite_ext.rb +53 -0
- data/lib/string_ext.rb +50 -0
- data/mite.cmd.gemspec +75 -0
- data/spec/mite_cmd/application_spec.rb +532 -0
- data/spec/mite_cmd/autocomplete_spec.rb +106 -0
- data/spec/mite_cmd_spec.rb +51 -0
- data/spec/mite_ext_spec.rb +99 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/string_ext_spec.rb +67 -0
- data/vendor/yolk-mite-rb-0.0.3/CHANGES.txt +11 -0
- data/vendor/yolk-mite-rb-0.0.3/LICENSE +20 -0
- data/vendor/yolk-mite-rb-0.0.3/README.textile +70 -0
- data/vendor/yolk-mite-rb-0.0.3/Rakefile +24 -0
- data/vendor/yolk-mite-rb-0.0.3/VERSION.yml +4 -0
- data/vendor/yolk-mite-rb-0.0.3/lib/mite-rb.rb +105 -0
- data/vendor/yolk-mite-rb-0.0.3/lib/mite/customer.rb +9 -0
- data/vendor/yolk-mite-rb-0.0.3/lib/mite/project.rb +16 -0
- data/vendor/yolk-mite-rb-0.0.3/lib/mite/service.rb +7 -0
- data/vendor/yolk-mite-rb-0.0.3/lib/mite/time_entry.rb +54 -0
- data/vendor/yolk-mite-rb-0.0.3/lib/mite/time_entry_group.rb +36 -0
- data/vendor/yolk-mite-rb-0.0.3/lib/mite/tracker.rb +34 -0
- data/vendor/yolk-mite-rb-0.0.3/lib/mite/user.rb +19 -0
- metadata +92 -0
@@ -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
|
data/lib/mite_ext.rb
ADDED
@@ -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
|
data/lib/string_ext.rb
ADDED
@@ -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
|
data/mite.cmd.gemspec
ADDED
@@ -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
|