cliver 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,7 @@
1
1
  # encoding: utf-8
2
+
3
+ require File.expand_path('../core_ext/file', __FILE__)
4
+
2
5
  require 'cliver/version'
3
6
  require 'cliver/dependency'
4
7
  require 'cliver/detector'
@@ -37,14 +40,28 @@ module Cliver
37
40
  # @overload (see Cliver::Dependency#initialize)
38
41
  # @param (see Cliver::Dependency#initialize)
39
42
  # @option options [Boolean] :strict (true) @see Cliver::Dependency::initialize
40
- # @raise (see Cliver::Dependency#assert!)
41
- # @return (see Cliver::Dependency#assert!)
43
+ # @raise (see Cliver::Dependency#detect!)
44
+ # @return (see Cliver::Dependency#detect!)
42
45
  def self.assert(*args, &block)
43
46
  options = args.last.kind_of?(Hash) ? args.pop : {}
44
47
  args << options.merge(:strict => true)
45
48
  Dependency::new(*args, &block).detect!
46
49
  end
47
50
 
51
+ # Verify an absolute-path to an executable.
52
+ # @param (see Cliver::Dependency#initialize)
53
+ # EXCEPT: executable must be a single, absolute path.
54
+ # @option options [Boolean] :strict (true) @see Cliver::Dependency::initialize
55
+ # @raise (see Cliver::Dependency#detect!)
56
+ # @return (see Cliver::Dependency#detect!)
57
+ def self.verify!(executable, *args, &block)
58
+ unless File.absolute_path?(executable)
59
+ raise ArgumentError, "executable path must be absolute, " +
60
+ "got '#{executable.inspect}'."
61
+ end
62
+ Dependency::new(executable, *args, &block).detect!
63
+ end
64
+
48
65
  extend self
49
66
 
50
67
  # Wraps Cliver::assert and returns truthy/false instead of raising
@@ -55,8 +55,22 @@ module Cliver
55
55
  @strict = options.fetch(:strict, false)
56
56
 
57
57
  @executables = Array(executables).dup.freeze
58
-
59
58
  @requirement = args unless args.empty?
59
+
60
+ check_compatibility!
61
+ end
62
+
63
+ # One of these things is not like the other ones...
64
+ # Some feature combinations just aren't compatible. This method ensures
65
+ # the the features selected for this object are compatible with each-other.
66
+ # @return [void]
67
+ # @raise [ArgumentError] if incompatibility found
68
+ def check_compatibility!
69
+ case
70
+ when @executables.any? {|exe| exe[File::SEPARATOR] && !File.absolute_path?(exe) }
71
+ # if the executable contains a path component, it *must* be absolute.
72
+ raise ArgumentError, "Relative-path executable requirements are not supported."
73
+ end
60
74
  end
61
75
 
62
76
  # Get all the installed versions of the api-compatible executables.
@@ -124,20 +138,18 @@ module Cliver
124
138
  # @api private
125
139
  # @raise [Cliver::Dependency::NotFound] with appropriate error message
126
140
  def raise_not_found!
127
- raise Dependency::NotFound.new <<-EOERR
128
- Could not find an executable #{@executables} on your path.
129
- EOERR
141
+ raise Dependency::NotFound.new(
142
+ "Could not find an executable #{@executables} on your path.")
130
143
  end
131
144
 
132
145
  # @api private
133
146
  # @raise [Cliver::Dependency::VersionMismatch] with appropriate error message
134
147
  # @param installed [Hash<String,String>] the found versions
135
148
  def raise_version_mismatch!(installed)
136
- raise Dependency::VersionMismatch.new <<-EOERR
137
- Could not find an executable #{executable_description} that matched the
138
- requirements #{requirements_description}.
139
- Found versions were #{installed.inspect}
140
- EOERR
149
+ raise Dependency::VersionMismatch.new(
150
+ "Could not find an executable #{executable_description} that " +
151
+ "matched the requirements #{requirements_description}. " +
152
+ "Found versions were #{installed.inspect}.")
141
153
  end
142
154
 
143
155
  # @api private
@@ -189,9 +201,15 @@ module Cliver
189
201
  paths = @path.sub('*', ENV['PATH']).split(File::PATH_SEPARATOR)
190
202
  cmds = strict? ? @executables.first(1) : @executables
191
203
 
204
+ detected_exes = []
192
205
  cmds.product(paths, exts).map do |cmd, path, ext|
193
- exe = File.join(path, "#{cmd}#{ext}")
194
- yield exe if File.executable?(exe)
206
+ exe = File.expand_path("#{cmd}#{ext}", path)
207
+
208
+ next unless File.executable?(exe)
209
+ next if detected_exes.include?(exe) # don't yield the same exe path 2x
210
+
211
+ detected_exes << exe
212
+ yield exe
195
213
  end
196
214
  end
197
215
  end
@@ -41,6 +41,13 @@ module Cliver
41
41
  # version number.
42
42
  def detect_version(executable_path)
43
43
  output = shell_out_and_capture version_command(executable_path).shelljoin
44
+ if $?.exitstatus == 127
45
+ raise Cliver::Dependency::NotFound.new(
46
+ "Could not find an executable at given path '#{executable_path}'." +
47
+ "If this path was not specified explicitly, it is probably a " +
48
+ "bug in [Cliver](https://github.com/yaauie/cliver/issues)."
49
+ )
50
+ end
44
51
  output[version_pattern]
45
52
  end
46
53
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Cliver
4
4
  # Cliver follows {http://semver.org SemVer}
5
- VERSION = '0.2.0'
5
+ VERSION = '0.2.1'
6
6
  end
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+
3
+ class File
4
+ # determine whether a String path is absolute.
5
+ # @example
6
+ # File.absolute_path?('foo') #=> false
7
+ # File.absolute_path?('/foo') #=> true
8
+ # File.absolute_path?('foo/bar') #=> false
9
+ # File.absolute_path?('/foo/bar') #=> true
10
+ # File.absolute_path?('C:foo/bar') #=> false
11
+ # File.absolute_path?('C:/foo/bar') #=> true
12
+ # @param [String] - a pathname
13
+ # @return [Boolean]
14
+ def self.absolute_path?(path)
15
+ false | File.dirname(path)[/\A([A-Z]:)?#{Regexp.escape(File::SEPARATOR)}/i]
16
+ end
17
+ end
@@ -32,14 +32,14 @@ describe Cliver do
32
32
  args << options
33
33
  end
34
34
 
35
- let(:path) { 'foo/bar:baz/bingo' }
35
+ let(:path) { '/foo/bar:/baz/bingo' }
36
36
  let(:executable) { 'doodle' }
37
37
  let(:requirement) { '~>1.1'}
38
38
 
39
39
  context 'when first-found version is sufficient' do
40
40
 
41
41
  let(:version_map) do
42
- {'baz/bingo/doodle' => '1.2.1'}
42
+ {'/baz/bingo/doodle' => '1.2.1'}
43
43
  end
44
44
 
45
45
  context '::assert' do
@@ -55,20 +55,94 @@ describe Cliver do
55
55
  end
56
56
  context '::detect' do
57
57
  let(:method) { :detect }
58
- it { should eq 'baz/bingo/doodle' }
58
+ it { should eq '/baz/bingo/doodle' }
59
59
  end
60
60
  context '::detect!' do
61
61
  let(:method) { :detect! }
62
62
  it 'should not raise' do
63
63
  expect { action }.to_not raise_exception
64
64
  end
65
- it { should eq 'baz/bingo/doodle' }
65
+ it { should eq '/baz/bingo/doodle' }
66
+ end
67
+ end
68
+
69
+ context '::verify!' do
70
+ let(:method) { :verify! }
71
+ let(:version_map) do
72
+ {'/baz/bingo/doodle' => '0.2.1',
73
+ '/baz/fiddle/doodle' => '1.1.4'}
74
+ end
75
+ let(:args) do
76
+ args = [executable]
77
+ args.concat Array(requirement)
78
+ args << options
79
+ end
80
+ context 'when a relative path is given' do
81
+ let(:executable) { 'foo/bar/doodle' }
82
+ it 'should raise' do
83
+ expect { action }.to raise_exception ArgumentError
84
+ end
85
+ end
86
+ context 'when an absolute path is given' do
87
+ context 'and that path is not found' do
88
+ let(:executable) { '/blip/boom' }
89
+ it 'should raise' do
90
+ expect { action }.to raise_exception Cliver::Dependency::NotFound
91
+ end
92
+ end
93
+ context 'and the executable at that path is sufficent' do
94
+ let(:executable) { '/baz/fiddle/doodle' }
95
+ it 'should not raise' do
96
+ expect { action }.to_not raise_exception Cliver::Dependency::NotFound
97
+ end
98
+ end
99
+ context 'and the executable at that path is not sufficent' do
100
+ let(:executable) { '/baz/bingo/doodle' }
101
+ it 'should raise' do
102
+ expect { action }.to raise_exception Cliver::Dependency::VersionMismatch
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ context 'when given executable as a path' do
109
+ let(:version_map) do
110
+ {'/baz/bingo/doodle' => '1.2.1'}
111
+ end
112
+ let(:path) { '/fiddle/foo:/deedle/dee'}
113
+
114
+ context 'that is absolute' do
115
+ let(:executable) { '/baz/bingo/doodle' }
116
+ %w(assert dependency_unmet? detect detect).each do |method_name|
117
+ context "::#{method_name}" do
118
+ let(:method) { method_name.to_sym }
119
+ it 'should only detect its version once' do
120
+ Cliver::Dependency.any_instance.
121
+ should_receive(:detect_version).
122
+ once.
123
+ and_call_original
124
+ action
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ context 'that is relative' do
131
+ let(:executable) { 'baz/bingo/doodle' }
132
+ %w(assert dependency_unmet? detect detect).each do |method_name|
133
+ context "::#{method_name}" do
134
+ let(:method) { method_name.to_sym }
135
+ it 'should raise an ArgumentError' do
136
+ expect { action }.to raise_exception ArgumentError
137
+ end
138
+ end
139
+ end
66
140
  end
67
141
  end
68
142
 
69
143
  context 'when first-found version insufficient' do
70
144
  let(:version_map) do
71
- {'baz/bingo/doodle' => '1.0.1'}
145
+ {'/baz/bingo/doodle' => '1.0.1'}
72
146
  end
73
147
  context '::assert' do
74
148
  let(:method) { :assert }
@@ -94,8 +168,8 @@ describe Cliver do
94
168
  context 'and when sufficient version found later on path' do
95
169
  let(:version_map) do
96
170
  {
97
- 'foo/bar/doodle' => '0.0.1',
98
- 'baz/bingo/doodle' => '1.1.0',
171
+ '/foo/bar/doodle' => '0.0.1',
172
+ '/baz/bingo/doodle' => '1.1.0',
99
173
  }
100
174
  end
101
175
  context '::assert' do
@@ -110,14 +184,14 @@ describe Cliver do
110
184
  end
111
185
  context '::detect' do
112
186
  let(:method) { :detect }
113
- it { should eq 'baz/bingo/doodle'}
187
+ it { should eq '/baz/bingo/doodle'}
114
188
  end
115
189
  context '::detect!' do
116
190
  let(:method) { :detect! }
117
191
  it 'should not raise' do
118
192
  expect { action }.to_not raise_exception
119
193
  end
120
- it { should eq 'baz/bingo/doodle' }
194
+ it { should eq '/baz/bingo/doodle' }
121
195
  end
122
196
  end
123
197
  end
@@ -154,26 +228,26 @@ describe Cliver do
154
228
  context 'and primary sufficient' do
155
229
  let(:version_map) do
156
230
  {
157
- 'baz/bingo/primary' => '1.1',
158
- 'foo/bar/fallback' => '1.1'
231
+ '/baz/bingo/primary' => '1.1',
232
+ '/foo/bar/fallback' => '1.1'
159
233
  }
160
234
  end
161
235
  context '::detect' do
162
236
  let(:method) { :detect }
163
- it { should eq 'baz/bingo/primary' }
237
+ it { should eq '/baz/bingo/primary' }
164
238
  end
165
239
  end
166
240
  context 'and primary insufficient' do
167
241
  let(:version_map) do
168
242
  {
169
- 'baz/bingo/primary' => '2.1',
170
- 'foo/bar/fallback' => '1.1'
243
+ '/baz/bingo/primary' => '2.1',
244
+ '/foo/bar/fallback' => '1.1'
171
245
  }
172
246
  end
173
247
  context 'the secondary' do
174
248
  context '::detect' do
175
249
  let(:method) { :detect }
176
- it { should eq 'foo/bar/fallback' }
250
+ it { should eq '/foo/bar/fallback' }
177
251
  end
178
252
  end
179
253
  end
@@ -182,12 +256,12 @@ describe Cliver do
182
256
  context 'and sufficient secondary does' do
183
257
  let(:version_map) do
184
258
  {
185
- 'foo/bar/fallback' => '1.1'
259
+ '/foo/bar/fallback' => '1.1'
186
260
  }
187
261
  end
188
262
  context '::detect' do
189
263
  let(:method) { :detect }
190
- it { should eq 'foo/bar/fallback' }
264
+ it { should eq '/foo/bar/fallback' }
191
265
  end
192
266
  end
193
267
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cliver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-07-08 00:00:00.000000000 Z
12
+ date: 2013-07-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -128,6 +128,7 @@ files:
128
128
  - lib/cliver/detector.rb
129
129
  - lib/cliver/filter.rb
130
130
  - lib/cliver/version.rb
131
+ - lib/core_ext/file.rb
131
132
  - spec/cliver/detector_spec.rb
132
133
  - spec/cliver_spec.rb
133
134
  - spec/spec_helper.rb