cliver 0.2.2 → 0.3.1
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/lib/cliver.rb +9 -5
- data/lib/cliver/dependency.rb +5 -4
- data/lib/cliver/detector.rb +3 -11
- data/lib/cliver/shell_capture.rb +35 -0
- data/lib/cliver/version.rb +1 -1
- data/lib/core_ext/file.rb +11 -3
- data/spec/cliver/detector_spec.rb +34 -0
- data/spec/cliver/shell_capture_spec.rb +42 -0
- data/spec/cliver_spec.rb +27 -16
- data/spec/support/executable_mock.rb +45 -0
- metadata +7 -2
data/lib/cliver.rb
CHANGED
@@ -4,6 +4,7 @@ require File.expand_path('../core_ext/file', __FILE__)
|
|
4
4
|
|
5
5
|
require 'cliver/version'
|
6
6
|
require 'cliver/dependency'
|
7
|
+
require 'cliver/shell_capture'
|
7
8
|
require 'cliver/detector'
|
8
9
|
require 'cliver/filter'
|
9
10
|
|
@@ -49,16 +50,19 @@ module Cliver
|
|
49
50
|
end
|
50
51
|
|
51
52
|
# Verify an absolute-path to an executable.
|
52
|
-
# @
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
53
|
+
# @overload verify!(executable, *requirements, options = {})
|
54
|
+
# @param executable [String] absolute path to an executable
|
55
|
+
# @param requirements (see Cliver::Dependency#initialize)
|
56
|
+
# @option options (see Cliver::Dependency::initialize)
|
57
|
+
# @raise (see Cliver::Dependency#detect!)
|
58
|
+
# @return (see Cliver::Dependency#detect!)
|
57
59
|
def self.verify!(executable, *args, &block)
|
58
60
|
unless File.absolute_path?(executable)
|
59
61
|
raise ArgumentError, "executable path must be absolute, " +
|
60
62
|
"got '#{executable.inspect}'."
|
61
63
|
end
|
64
|
+
options = args.last.kind_of?(Hash) ? args.pop : {}
|
65
|
+
args << options.merge(:path => '.') # ensure path non-empty.
|
62
66
|
Dependency::new(executable, *args, &block).detect!
|
63
67
|
end
|
64
68
|
|
data/lib/cliver/dependency.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'rubygems/requirement'
|
3
|
+
require 'set'
|
3
4
|
|
4
5
|
module Cliver
|
5
6
|
# This is how a dependency is specified.
|
@@ -197,18 +198,18 @@ module Cliver
|
|
197
198
|
def find_executables
|
198
199
|
return enum_for(:find_executables) unless block_given?
|
199
200
|
|
200
|
-
exts = ENV.has_key?('PATHEXT') ? ENV.fetch('PATHEXT').split(';') : [''
|
201
|
+
exts = (ENV.has_key?('PATHEXT') ? ENV.fetch('PATHEXT').split(';') : []) << ''
|
201
202
|
paths = @path.sub('*', ENV['PATH']).split(File::PATH_SEPARATOR)
|
203
|
+
raise ArgumentError.new('No PATH to search!') if paths.empty?
|
202
204
|
cmds = strict? ? @executables.first(1) : @executables
|
203
205
|
|
204
|
-
|
206
|
+
lookup_cache = Set.new
|
205
207
|
cmds.product(paths, exts).map do |cmd, path, ext|
|
206
208
|
exe = File.absolute_path?(cmd) ? cmd : File.expand_path("#{cmd}#{ext}", path)
|
207
209
|
|
210
|
+
next unless lookup_cache.add?(exe) # don't yield the same exe path 2x
|
208
211
|
next unless File.executable?(exe)
|
209
|
-
next if detected_exes.include?(exe) # don't yield the same exe path 2x
|
210
212
|
|
211
|
-
detected_exes << exe
|
212
213
|
yield exe
|
213
214
|
end
|
214
215
|
end
|
data/lib/cliver/detector.rb
CHANGED
@@ -40,15 +40,15 @@ module Cliver
|
|
40
40
|
# @return [String] - should be contain {Gem::Version}-parsable
|
41
41
|
# version number.
|
42
42
|
def detect_version(executable_path)
|
43
|
-
|
44
|
-
|
43
|
+
capture = ShellCapture.new(version_command(executable_path))
|
44
|
+
unless capture.command_found
|
45
45
|
raise Cliver::Dependency::NotFound.new(
|
46
46
|
"Could not find an executable at given path '#{executable_path}'." +
|
47
47
|
"If this path was not specified explicitly, it is probably a " +
|
48
48
|
"bug in [Cliver](https://github.com/yaauie/cliver/issues)."
|
49
49
|
)
|
50
50
|
end
|
51
|
-
|
51
|
+
capture.stdout[version_pattern] || capture.stderr[version_pattern]
|
52
52
|
end
|
53
53
|
|
54
54
|
# This is the interface that any detector must have.
|
@@ -80,13 +80,5 @@ module Cliver
|
|
80
80
|
def version_command(executable_path)
|
81
81
|
[executable_path, *Array(command_arg)]
|
82
82
|
end
|
83
|
-
|
84
|
-
private
|
85
|
-
|
86
|
-
# @api private
|
87
|
-
# A boundary that is useful for testing.
|
88
|
-
def shell_out_and_capture(command)
|
89
|
-
`#{command} 2>&1`
|
90
|
-
end
|
91
83
|
end
|
92
84
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module Cliver
|
4
|
+
class ShellCapture
|
5
|
+
attr_reader :stdout, :stderr, :command_found
|
6
|
+
|
7
|
+
# @overlaod initialize(command)
|
8
|
+
# @param command [String] the command to run
|
9
|
+
# @overload initialize(command)
|
10
|
+
# @param command [Array<String>] the command to run; elements in
|
11
|
+
# the supplied array will be shelljoined.
|
12
|
+
# @return [void]
|
13
|
+
def initialize(command)
|
14
|
+
command = command.shelljoin if command.kind_of?(Array)
|
15
|
+
@stdout = @stderr = ''
|
16
|
+
begin
|
17
|
+
Open3.popen3(command) do |i, o, e|
|
18
|
+
@stdout = o.read.chomp
|
19
|
+
@stderr = e.read.chomp
|
20
|
+
end
|
21
|
+
# Fix for ruby 1.8.7 (and probably earlier):
|
22
|
+
# Open3.popen3 does not raise anything there, but the error goes to STDERR.
|
23
|
+
if @stderr =~ /open3.rb:\d+:in `exec': No such file or directory -.*\(Errno::ENOENT\)/ or
|
24
|
+
@stderr =~ /An exception occurred in a forked block\W+No such file or directory.*\(Errno::ENOENT\)/
|
25
|
+
@stderr = ''
|
26
|
+
@command_found = false
|
27
|
+
else
|
28
|
+
@command_found = true
|
29
|
+
end
|
30
|
+
rescue Errno::ENOENT, IOError
|
31
|
+
@command_found = false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/cliver/version.rb
CHANGED
data/lib/core_ext/file.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
+
# Core-Extensions on File
|
3
4
|
class File
|
4
5
|
# determine whether a String path is absolute.
|
5
6
|
# @example
|
@@ -9,10 +10,17 @@ class File
|
|
9
10
|
# File.absolute_path?('/foo/bar') #=> true
|
10
11
|
# File.absolute_path?('C:foo/bar') #=> false
|
11
12
|
# File.absolute_path?('C:/foo/bar') #=> true
|
12
|
-
# @param [String] - a pathname
|
13
|
+
# @param path [String] - a pathname
|
13
14
|
# @return [Boolean]
|
14
|
-
def self.absolute_path?(path)
|
15
|
-
|
15
|
+
def self.absolute_path?(path, platform = :default)
|
16
|
+
pattern = case platform
|
17
|
+
when :default then ABSOLUTE_PATH_PATTERN
|
18
|
+
when :windows then WINDOWS_ABSOLUTE_PATH_PATTERN
|
19
|
+
when :posix then POSIX_ABSOLUTE_PATH_PATTERN
|
20
|
+
else raise ArgumentError, "Unsupported platform '#{platform.inspect}'"
|
21
|
+
end
|
22
|
+
|
23
|
+
false | path[pattern]
|
16
24
|
end
|
17
25
|
|
18
26
|
unless defined?(POSIX_ABSOLUTE_PATH_PATTERN)
|
@@ -41,4 +41,38 @@ describe Cliver::Detector do
|
|
41
41
|
its(:command_arg) { should eq [version_arg] }
|
42
42
|
its(:version_pattern) { should eq regexp_arg }
|
43
43
|
end
|
44
|
+
|
45
|
+
context 'detecting a command' do
|
46
|
+
before(:each) do
|
47
|
+
Cliver::ShellCapture.stub(:new => capture)
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'that reports version on stdout' do
|
51
|
+
let(:capture) { double('capture', :stdout => '1.1',
|
52
|
+
:stderr => 'Warning: There is a monkey 1.2 metres left of you.',
|
53
|
+
:command_found => true) }
|
54
|
+
|
55
|
+
it 'should prefer the stdout output' do
|
56
|
+
expect(detector.detect_version('foo')).to eq('1.1')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'that reports version on stderr' do
|
61
|
+
let(:capture) { double('capture', :stdout => '',
|
62
|
+
:stderr => 'Version: 1.666',
|
63
|
+
:command_found => true) }
|
64
|
+
|
65
|
+
it 'should prefer the stderr output' do
|
66
|
+
expect(detector.detect_version('foo')).to eq('1.666')
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'that does not exist' do
|
71
|
+
let(:capture) { Cliver::ShellCapture.new('acommandnosystemshouldhave123') }
|
72
|
+
|
73
|
+
it 'should raise an exception' do
|
74
|
+
expect { detector.detect_version('foo') }.to raise_error(Cliver::Dependency::NotFound)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
44
78
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'cliver'
|
3
|
+
|
4
|
+
describe Cliver::ShellCapture do
|
5
|
+
let(:test_command) { 'test command' }
|
6
|
+
subject { Cliver::ShellCapture.new(test_command) }
|
7
|
+
|
8
|
+
context 'a command that exists' do
|
9
|
+
let(:intended_stdout) { StringIO.new('1.1.1').tap(&:rewind) }
|
10
|
+
let(:intended_stderr) { StringIO.new('foo baar 1').tap(&:rewind) }
|
11
|
+
let(:intended_stdin) { StringIO.new('').tap(&:rewind) }
|
12
|
+
|
13
|
+
['test command', %w(test command)].each do |input|
|
14
|
+
context "with #{input.class.name} input" do
|
15
|
+
let(:test_command) { input }
|
16
|
+
|
17
|
+
before(:each) do
|
18
|
+
Open3.should_receive(:popen3) do |*args|
|
19
|
+
args.size.should eq 1
|
20
|
+
args.first.should == 'test command'
|
21
|
+
end.and_yield(intended_stdin, intended_stdout, intended_stderr)
|
22
|
+
end
|
23
|
+
|
24
|
+
its(:stdout) { should eq '1.1.1' }
|
25
|
+
its(:stderr) { should eq 'foo baar 1' }
|
26
|
+
its(:command_found) { should be_true }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'looking for a command that does not exist' do
|
32
|
+
before(:each) do
|
33
|
+
Open3.should_receive(:popen3) do |command|
|
34
|
+
command.should eq test_command
|
35
|
+
raise Errno::ENOENT.new("No such file or directory - #{test_command}")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
its(:stdout) { should eq '' }
|
39
|
+
its(:stderr) { should eq '' }
|
40
|
+
its(:command_found) { should be_false }
|
41
|
+
end
|
42
|
+
end
|
data/spec/cliver_spec.rb
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'cliver'
|
3
3
|
require 'spec_helper'
|
4
|
+
require File.expand_path('../support/executable_mock', __FILE__)
|
5
|
+
|
6
|
+
RSpec::Matchers.define :be_filesystem_equivalent_of do |expected|
|
7
|
+
match do |actual|
|
8
|
+
ExecutableMock.new(expected) == actual
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
4
12
|
|
5
13
|
describe Cliver do
|
6
14
|
# The setup. Your test will likeley interact with subject.
|
@@ -10,29 +18,32 @@ describe Cliver do
|
|
10
18
|
# These can get overridden in context blocks
|
11
19
|
let(:method) { raise ArgumentError, 'spec didn\'t specify :method' }
|
12
20
|
let(:args) { raise ArgumentError, 'spec didn\'t specify :args' }
|
13
|
-
let(:block) {
|
21
|
+
let(:block) { version_directory.method(:version) }
|
22
|
+
|
23
|
+
# BecauseWindows. This enables us to mock out File::executable?
|
24
|
+
# and the responses from our detectors given any representation
|
25
|
+
# of a file path.
|
26
|
+
let(:version_directory) do
|
27
|
+
ExecutableMock::Registry.new(version_map)
|
28
|
+
end
|
14
29
|
|
15
30
|
before(:each) do
|
16
|
-
File.stub(:executable
|
17
|
-
# Cliver::Dependency.any_instance.stub(:detect_version) do |arg|
|
18
|
-
# version_map[arg]
|
19
|
-
# end
|
31
|
+
File.stub(:executable?, &version_directory.method(:executable?))
|
20
32
|
end
|
21
33
|
|
22
34
|
let(:options) do
|
23
35
|
{
|
24
|
-
:path => path,
|
36
|
+
:path => path.join(File::PATH_SEPARATOR),
|
25
37
|
:executable => executable,
|
26
38
|
}
|
27
39
|
end
|
28
|
-
let(:executables) { version_map.keys }
|
29
40
|
let(:args) do
|
30
41
|
args = [Array(executable)]
|
31
42
|
args.concat Array(requirement)
|
32
43
|
args << options
|
33
44
|
end
|
34
45
|
|
35
|
-
let(:path) { '/foo/bar
|
46
|
+
let(:path) { ['/foo/bar','/baz/bingo'] }
|
36
47
|
let(:executable) { 'doodle' }
|
37
48
|
let(:requirement) { '~>1.1'}
|
38
49
|
|
@@ -55,14 +66,14 @@ describe Cliver do
|
|
55
66
|
end
|
56
67
|
context '::detect' do
|
57
68
|
let(:method) { :detect }
|
58
|
-
it { should
|
69
|
+
it { should be_filesystem_equivalent_of '/baz/bingo/doodle' }
|
59
70
|
end
|
60
71
|
context '::detect!' do
|
61
72
|
let(:method) { :detect! }
|
62
73
|
it 'should not raise' do
|
63
74
|
expect { action }.to_not raise_exception
|
64
75
|
end
|
65
|
-
it { should
|
76
|
+
it { should be_filesystem_equivalent_of '/baz/bingo/doodle' }
|
66
77
|
end
|
67
78
|
end
|
68
79
|
|
@@ -137,7 +148,7 @@ describe Cliver do
|
|
137
148
|
let(:version_map) do
|
138
149
|
{'/baz/bingo/doodle' => '1.2.1'}
|
139
150
|
end
|
140
|
-
let(:path) { '/fiddle/foo
|
151
|
+
let(:path) { ['/fiddle/foo','/deedle/dee'] }
|
141
152
|
|
142
153
|
context 'that is absolute' do
|
143
154
|
let(:executable) { '/baz/bingo/doodle' }
|
@@ -212,14 +223,14 @@ describe Cliver do
|
|
212
223
|
end
|
213
224
|
context '::detect' do
|
214
225
|
let(:method) { :detect }
|
215
|
-
it { should
|
226
|
+
it { should be_filesystem_equivalent_of '/baz/bingo/doodle' }
|
216
227
|
end
|
217
228
|
context '::detect!' do
|
218
229
|
let(:method) { :detect! }
|
219
230
|
it 'should not raise' do
|
220
231
|
expect { action }.to_not raise_exception
|
221
232
|
end
|
222
|
-
it { should
|
233
|
+
it { should be_filesystem_equivalent_of '/baz/bingo/doodle' }
|
223
234
|
end
|
224
235
|
end
|
225
236
|
end
|
@@ -262,7 +273,7 @@ describe Cliver do
|
|
262
273
|
end
|
263
274
|
context '::detect' do
|
264
275
|
let(:method) { :detect }
|
265
|
-
it { should
|
276
|
+
it { should be_filesystem_equivalent_of '/baz/bingo/primary' }
|
266
277
|
end
|
267
278
|
end
|
268
279
|
context 'and primary insufficient' do
|
@@ -275,7 +286,7 @@ describe Cliver do
|
|
275
286
|
context 'the secondary' do
|
276
287
|
context '::detect' do
|
277
288
|
let(:method) { :detect }
|
278
|
-
it { should
|
289
|
+
it { should be_filesystem_equivalent_of '/foo/bar/fallback' }
|
279
290
|
end
|
280
291
|
end
|
281
292
|
end
|
@@ -289,7 +300,7 @@ describe Cliver do
|
|
289
300
|
end
|
290
301
|
context '::detect' do
|
291
302
|
let(:method) { :detect }
|
292
|
-
it { should
|
303
|
+
it { should be_filesystem_equivalent_of '/foo/bar/fallback' }
|
293
304
|
end
|
294
305
|
end
|
295
306
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class ExecutableMock
|
4
|
+
def initialize(path)
|
5
|
+
@path = _to_platform_abs_path(path)
|
6
|
+
end
|
7
|
+
attr_reader :path
|
8
|
+
attr_reader :version
|
9
|
+
|
10
|
+
def == (other_path)
|
11
|
+
other_path = _to_platform_abs_path(other_path)
|
12
|
+
if other_path[/[A-Z]:/i] # windows
|
13
|
+
pattern = /\A#{self.path}(#{(ENV['PATHEXT']||'').split(';').map(&Regexp::method(:escape)).join('|')})?\Z/i
|
14
|
+
pattern =~ other_path
|
15
|
+
else # posix
|
16
|
+
self.path == other_path
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def _to_platform_abs_path(source)
|
23
|
+
(File::absolute_path?(source, :windows) && !File::absolute_path?(source, :posix)) ?
|
24
|
+
source.tr('\\','/') :
|
25
|
+
File.expand_path(source)
|
26
|
+
end
|
27
|
+
|
28
|
+
class Registry
|
29
|
+
def initialize(version_map)
|
30
|
+
@registry = {}
|
31
|
+
version_map.each do |path,version|
|
32
|
+
@registry[ExecutableMock.new(path)] = version
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def executable?(path)
|
37
|
+
false | version(path)
|
38
|
+
end
|
39
|
+
|
40
|
+
def version(path)
|
41
|
+
key = @registry.keys.find {|exe| exe == path }
|
42
|
+
key && @registry[key]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
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.
|
4
|
+
version: 0.3.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-
|
12
|
+
date: 2013-10-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -127,12 +127,15 @@ files:
|
|
127
127
|
- lib/cliver/dependency.rb
|
128
128
|
- lib/cliver/detector.rb
|
129
129
|
- lib/cliver/filter.rb
|
130
|
+
- lib/cliver/shell_capture.rb
|
130
131
|
- lib/cliver/version.rb
|
131
132
|
- lib/core_ext/file.rb
|
132
133
|
- spec/cliver/detector_spec.rb
|
134
|
+
- spec/cliver/shell_capture_spec.rb
|
133
135
|
- spec/cliver_spec.rb
|
134
136
|
- spec/core_ext/file_spec.rb
|
135
137
|
- spec/spec_helper.rb
|
138
|
+
- spec/support/executable_mock.rb
|
136
139
|
homepage: https://www.github.com/yaauie/cliver
|
137
140
|
licenses:
|
138
141
|
- MIT
|
@@ -160,7 +163,9 @@ specification_version: 3
|
|
160
163
|
summary: Cross-platform version constraints for cli tools
|
161
164
|
test_files:
|
162
165
|
- spec/cliver/detector_spec.rb
|
166
|
+
- spec/cliver/shell_capture_spec.rb
|
163
167
|
- spec/cliver_spec.rb
|
164
168
|
- spec/core_ext/file_spec.rb
|
165
169
|
- spec/spec_helper.rb
|
170
|
+
- spec/support/executable_mock.rb
|
166
171
|
has_rdoc: yard
|