cliver 0.1.5 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +76 -11
- data/lib/cliver.rb +37 -9
- data/lib/cliver/dependency.rb +198 -0
- data/lib/cliver/detector.rb +29 -11
- data/lib/cliver/filter.rb +11 -1
- data/lib/cliver/version.rb +1 -1
- data/spec/cliver/detector_spec.rb +2 -2
- data/spec/cliver_spec.rb +194 -22
- data/spec/spec_helper.rb +16 -0
- metadata +5 -8
- data/lib/cliver/assertion.rb +0 -89
- data/lib/cliver/which.rb +0 -17
- data/lib/cliver/which/posix.rb +0 -22
- data/lib/cliver/which/windows.rb +0 -25
- data/spec/cliver/assertion_spec.rb +0 -156
data/README.md
CHANGED
@@ -2,42 +2,94 @@
|
|
2
2
|
|
3
3
|
Sometimes Ruby apps shell out to command-line executables, but there is no
|
4
4
|
standard way to ensure those underlying dependencies are met. Users usually
|
5
|
-
find out via a nasty stack-trace and whatever wasn't captured on stderr
|
5
|
+
find out via a nasty stack-trace and whatever wasn't captured on stderr, or by
|
6
|
+
the odd behavior exposed by a version mismatch.
|
6
7
|
|
7
|
-
`Cliver` is a simple gem that provides an easy way to
|
8
|
+
`Cliver` is a simple gem that provides an easy way to detect and use
|
8
9
|
command-line dependencies. Under the covers, it uses [rubygems/requirements][]
|
9
10
|
so it supports the version requirements you're used to providing in your
|
10
11
|
gemspec.
|
11
12
|
|
12
13
|
## Usage
|
13
14
|
|
15
|
+
### Detect and Detect!
|
16
|
+
|
17
|
+
The detect methods search your entire path until they find a matching executable
|
18
|
+
or run out of places to look.
|
19
|
+
|
14
20
|
```ruby
|
15
|
-
|
16
|
-
Cliver.
|
17
|
-
|
21
|
+
# no version requirements
|
22
|
+
Cliver.detect('subl')
|
23
|
+
# => '/Users/yaauie/.bin/subl'
|
24
|
+
|
25
|
+
# one version requirement
|
26
|
+
Cliver.detect('bzip2', '~> 1.0.6')
|
27
|
+
# => '/usr/bin/bzip2'
|
28
|
+
|
29
|
+
# many version requirements
|
30
|
+
Cliver.detect('racc', '>= 1.0', '< 1.4.9')
|
31
|
+
# => '/Users/yaauie/.rbenv/versions/1.9.3-p194/bin/racc'
|
32
|
+
|
33
|
+
# dependency not met
|
34
|
+
Cliver.detect('racc', '~> 10.4.9')
|
35
|
+
# => nil
|
36
|
+
|
37
|
+
# detect! raises Cliver::Dependency::NotMet exceptions when the dependency
|
38
|
+
# cannot be met.
|
39
|
+
Cliver.detect!('ruby', '1.8.5')
|
40
|
+
# Cliver::Dependency::VersionMismatch
|
41
|
+
# Could not find an executable ruby that matched the
|
42
|
+
# requirements '1.8.5'. Found versions were {'/usr/bin/ruby'=> '1.8.7'}
|
43
|
+
Cliver.detect!('asdfasdf')
|
44
|
+
# Cliver::Dependency::NotFound
|
45
|
+
# Could not find an executable asdfasdf on your path
|
18
46
|
```
|
19
47
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
48
|
+
### Assert
|
49
|
+
|
50
|
+
The assert method is useful when you do not have control over how the
|
51
|
+
dependency is shelled-out to and require that the first matching executable on
|
52
|
+
your path satisfies your version requirements. It is the equivalent of the
|
53
|
+
detect! method with `strict: true` option.
|
24
54
|
|
25
55
|
## Advanced Usage:
|
26
56
|
|
57
|
+
### Version Detectors
|
58
|
+
|
27
59
|
Some programs don't provide nice 'version 1.2.3' strings in their `--version`
|
28
60
|
output; `Cliver` lets you provide your own version detector with a pattern.
|
29
61
|
|
30
62
|
```ruby
|
31
63
|
Cliver.assert('python', '~> 1.7',
|
32
|
-
detector:
|
64
|
+
detector: /(?<=Python )[0-9][.0-9a-z]+/)
|
33
65
|
```
|
34
66
|
|
35
67
|
Other programs don't provide a standard `--version`; `Cliver::Detector` also
|
36
68
|
allows you to provide your own arg to get the version:
|
37
69
|
|
70
|
+
```ruby
|
71
|
+
# single-argument command
|
72
|
+
Cliver.assert('janky', '~> 10.1.alpha',
|
73
|
+
detector: '--release-version')
|
74
|
+
|
75
|
+
# multi-argument command
|
76
|
+
Cliver.detect('ruby', '~> 1.8.7',
|
77
|
+
detector: [['-e', 'puts RUBY_VERSION']])
|
78
|
+
```
|
79
|
+
|
80
|
+
You can use both custom pattern and custom command by supplying an array:
|
81
|
+
|
38
82
|
```ruby
|
39
83
|
Cliver.assert('janky', '~> 10.1.alpha',
|
40
|
-
detector:
|
84
|
+
detector: ['--release-version', /.*/])
|
85
|
+
```
|
86
|
+
|
87
|
+
And even supply multiple arguments in an Array, too:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
# multi-argument command
|
91
|
+
Cliver.detect('ruby', '~> 1.8.7',
|
92
|
+
detector: ['-e', 'puts RUBY_VERSION'])
|
41
93
|
```
|
42
94
|
|
43
95
|
Alternatively, you can supply your own detector (anything that responds to
|
@@ -55,6 +107,8 @@ And since some programs don't always spit out nice semver-friendly version
|
|
55
107
|
numbers at all, a filter proc can be supplied to clean it up. Note how the
|
56
108
|
filter is applied to both your requirements and the executable's output:
|
57
109
|
|
110
|
+
### Filters
|
111
|
+
|
58
112
|
```ruby
|
59
113
|
Cliver.assert('built-thing', '~> 2013.4r8273',
|
60
114
|
filter: proc { |ver| ver.tr('r','.') })
|
@@ -63,6 +117,17 @@ Cliver.assert('built-thing', '~> 2013.4r8273',
|
|
63
117
|
Since `Cliver` uses `Gem::Requirement` for version comparrisons, it obeys all
|
64
118
|
the same rules including pre-release semantics.
|
65
119
|
|
120
|
+
### Search Path
|
121
|
+
|
122
|
+
By default, Cliver uses `ENV['PATH']` as its search path, but you can provide
|
123
|
+
your own. If the asterisk symbol (`*`) is included in your string, it is
|
124
|
+
replaced `ENV['PATH']`.
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
Cliver.detect('gadget', path: './bins/:*')
|
128
|
+
# => 'Users/yaauie/src/project-a/bins/gadget'
|
129
|
+
```
|
130
|
+
|
66
131
|
## Supported Platforms
|
67
132
|
|
68
133
|
The goal is to have full support for all platforms running ruby >= 1.9.2,
|
data/lib/cliver.rb
CHANGED
@@ -1,20 +1,48 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'cliver/version'
|
3
|
-
require 'cliver/
|
4
|
-
require 'cliver/assertion'
|
3
|
+
require 'cliver/dependency'
|
5
4
|
require 'cliver/detector'
|
6
5
|
require 'cliver/filter'
|
7
6
|
|
8
7
|
# Cliver is tool for making dependency assertions against
|
9
8
|
# command-line executables.
|
10
9
|
module Cliver
|
11
|
-
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
# @
|
10
|
+
|
11
|
+
# The primary interface for the Cliver gem allows detection of an executable
|
12
|
+
# on your path that matches a version requirement, or raise an appropriate
|
13
|
+
# exception to make resolution simple and straight-forward.
|
14
|
+
# @see Cliver::Dependency
|
15
|
+
# @overload (see Cliver::Dependency#initialize)
|
16
|
+
# @param (see Cliver::Dependency#initialize)
|
17
|
+
# @raise (see Cliver::Dependency#detect!)
|
18
|
+
# @return (see Cliver::Dependency#detect!)
|
19
|
+
def self.detect!(*args, &block)
|
20
|
+
Dependency::new(*args, &block).detect!
|
21
|
+
end
|
22
|
+
|
23
|
+
# A non-raising variant of {::detect!}, simply returns false if dependency
|
24
|
+
# cannot be found.
|
25
|
+
# @see Cliver::Dependency
|
26
|
+
# @overload (see Cliver::Dependency#initialize)
|
27
|
+
# @param (see Cliver::Dependency#initialize)
|
28
|
+
# @raise (see Cliver::Dependency#detect)
|
29
|
+
# @return (see Cliver::Dependency#detect)
|
30
|
+
def self.detect(*args, &block)
|
31
|
+
Dependency::new(*args, &block).detect
|
32
|
+
end
|
33
|
+
|
34
|
+
# A legacy interface for {::detect} with the option `strict: true`, ensures
|
35
|
+
# that the first executable on your path matches the requirements.
|
36
|
+
# @see Cliver::Dependency
|
37
|
+
# @overload (see Cliver::Dependency#initialize)
|
38
|
+
# @param (see Cliver::Dependency#initialize)
|
39
|
+
# @option options [Boolean] :strict (true) @see Cliver::Dependency::initialize
|
40
|
+
# @raise (see Cliver::Dependency#assert!)
|
41
|
+
# @return (see Cliver::Dependency#assert!)
|
16
42
|
def self.assert(*args, &block)
|
17
|
-
|
43
|
+
options = args.last.kind_of?(Hash) ? args.pop : {}
|
44
|
+
args << options.merge(:strict => true)
|
45
|
+
Dependency::new(*args, &block).detect!
|
18
46
|
end
|
19
47
|
|
20
48
|
extend self
|
@@ -28,7 +56,7 @@ module Cliver
|
|
28
56
|
def dependency_unmet?(*args, &block)
|
29
57
|
Cliver.assert(*args, &block)
|
30
58
|
false
|
31
|
-
rescue
|
59
|
+
rescue Dependency::NotMet => error
|
32
60
|
# Cliver::Assertion::VersionMismatch -> 'Version Mismatch'
|
33
61
|
reason = error.class.name.split(':').last.gsub(/([a-z])([A-Z])/, '\\1 \\2')
|
34
62
|
"#{reason}: #{error.message}"
|
@@ -0,0 +1,198 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rubygems/requirement'
|
3
|
+
|
4
|
+
module Cliver
|
5
|
+
# This is how a dependency is specified.
|
6
|
+
class Dependency
|
7
|
+
|
8
|
+
# An exception class raised when assertion is not met
|
9
|
+
NotMet = Class.new(ArgumentError)
|
10
|
+
|
11
|
+
# An exception that is raised when executable present, but
|
12
|
+
# no version that matches the requirements is present.
|
13
|
+
VersionMismatch = Class.new(Dependency::NotMet)
|
14
|
+
|
15
|
+
# An exception that is raised when executable is not present at all.
|
16
|
+
NotFound = Class.new(Dependency::NotMet)
|
17
|
+
|
18
|
+
# A pattern for extracting a {Gem::Version}-parsable version
|
19
|
+
PARSABLE_GEM_VERSION = /[0-9]+(.[0-9]+){0,4}(.[a-zA-Z0-9]+)?/.freeze
|
20
|
+
|
21
|
+
# @overload initialize(executables, *requirements, options = {})
|
22
|
+
# @param executables [String,Array<String>] api-compatible executable names
|
23
|
+
# e.g, ['python2','python']
|
24
|
+
# @param requirements [Array<String>, String] splat of strings
|
25
|
+
# whose elements follow the pattern
|
26
|
+
# [<operator>] <version>
|
27
|
+
# Where <operator> is optional (default '='') and in the set
|
28
|
+
# '=', '!=', '>', '<', '>=', '<=', or '~>'
|
29
|
+
# And <version> is dot-separated integers with optional
|
30
|
+
# alphanumeric pre-release suffix. See also
|
31
|
+
# {http://docs.rubygems.org/read/chapter/16 Specifying Versions}
|
32
|
+
# @param options [Hash<Symbol,Object>]
|
33
|
+
# @option options [Cliver::Detector] :detector (Detector.new)
|
34
|
+
# @option options [#to_proc, Object] :detector (see Detector::generate)
|
35
|
+
# @option options [#to_proc] :filter ({Cliver::Filter::IDENTITY})
|
36
|
+
# @option options [Boolean] :strict (false)
|
37
|
+
# true - fail if first match on path fails
|
38
|
+
# to meet version requirements.
|
39
|
+
# This is used for Cliver::assert.
|
40
|
+
# false - continue looking on path until a
|
41
|
+
# sufficient version is found.
|
42
|
+
# @option options [String] :path ('*') the path on which to search
|
43
|
+
# for executables. If an asterisk (`*`) is
|
44
|
+
# included in the supplied string, it is
|
45
|
+
# replaced with `ENV['PATH']`
|
46
|
+
#
|
47
|
+
# @yieldparam executable_path [String] (see Detector#detect_version)
|
48
|
+
# @yieldreturn [String] containing a version that, once filtered, can be
|
49
|
+
# used for comparrison.
|
50
|
+
def initialize(executables, *args, &detector)
|
51
|
+
options = args.last.kind_of?(Hash) ? args.pop : {}
|
52
|
+
@detector = Detector::generate(detector || options[:detector])
|
53
|
+
@filter = options.fetch(:filter, Filter::IDENTITY).extend(Filter)
|
54
|
+
@path = options.fetch(:path, '*')
|
55
|
+
@strict = options.fetch(:strict, false)
|
56
|
+
|
57
|
+
@executables = Array(executables).dup.freeze
|
58
|
+
|
59
|
+
@requirement = args unless args.empty?
|
60
|
+
end
|
61
|
+
|
62
|
+
# Get all the installed versions of the api-compatible executables.
|
63
|
+
# If a block is given, it yields once per found executable, lazily.
|
64
|
+
# @yieldparam executable_path [String]
|
65
|
+
# @yieldparam version [String]
|
66
|
+
# @yieldreturn [Boolean] - true if search should stop.
|
67
|
+
# @return [Hash<String,String>] executable_path, version
|
68
|
+
def installed_versions
|
69
|
+
return enum_for(:installed_versions) unless block_given?
|
70
|
+
|
71
|
+
find_executables.each do |executable_path|
|
72
|
+
version = detect_version(executable_path)
|
73
|
+
|
74
|
+
break(2) if yield(executable_path, version)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# The non-raise variant of {#detect!}
|
79
|
+
# @return (see #detect!)
|
80
|
+
# or nil if no match found.
|
81
|
+
def detect
|
82
|
+
detect!
|
83
|
+
rescue Dependency::NotMet
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
|
87
|
+
# Detects an installed version of the executable that matches the
|
88
|
+
# requirements.
|
89
|
+
# @return [String] path to an executable that meets the requirements
|
90
|
+
# @raise [Cliver::Dependency::NotMet] if no match found
|
91
|
+
def detect!
|
92
|
+
installed = {}
|
93
|
+
installed_versions.each do |path, version|
|
94
|
+
installed[path] = version
|
95
|
+
return path if requirement_satisfied_by?(version)
|
96
|
+
strict?
|
97
|
+
end
|
98
|
+
|
99
|
+
# dependency not met. raise the appropriate error.
|
100
|
+
raise_not_found! if installed.empty?
|
101
|
+
raise_version_mismatch!(installed)
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
# @api private
|
107
|
+
# @return [Gem::Requirement]
|
108
|
+
def filtered_requirement
|
109
|
+
@filtered_requirement ||= begin
|
110
|
+
Gem::Requirement.new(@filter.requirements(@requirement))
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# @api private
|
115
|
+
# @param raw_version [String]
|
116
|
+
# @return [Boolean]
|
117
|
+
def requirement_satisfied_by?(raw_version)
|
118
|
+
return true unless @requirement
|
119
|
+
parsable_version = @filter.apply(raw_version)[PARSABLE_GEM_VERSION]
|
120
|
+
parsable_version || raise(ArgumentError) # TODO: make descriptive
|
121
|
+
filtered_requirement.satisfied_by? Gem::Version.new(parsable_version)
|
122
|
+
end
|
123
|
+
|
124
|
+
# @api private
|
125
|
+
# @raise [Cliver::Dependency::NotFound] with appropriate error message
|
126
|
+
def raise_not_found!
|
127
|
+
raise Dependency::NotFound.new <<-EOERR
|
128
|
+
Could not find an executable #{@executables} on your path.
|
129
|
+
EOERR
|
130
|
+
end
|
131
|
+
|
132
|
+
# @api private
|
133
|
+
# @raise [Cliver::Dependency::VersionMismatch] with appropriate error message
|
134
|
+
# @param installed [Hash<String,String>] the found versions
|
135
|
+
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
|
141
|
+
end
|
142
|
+
|
143
|
+
# @api private
|
144
|
+
# @return [String] a plain-language representation of the executables
|
145
|
+
# for which we were searching
|
146
|
+
def executable_description
|
147
|
+
quoted_exes = @executables.map {|exe| "'#{exe}'" }
|
148
|
+
return quoted_exes.first if quoted_exes.size == 1
|
149
|
+
|
150
|
+
last_quoted_exec = quoted_exes.pop
|
151
|
+
"#{quoted_exes.join(', ')} or #{last_quoted_exec}"
|
152
|
+
end
|
153
|
+
|
154
|
+
# @api private
|
155
|
+
# @return [String] a plain-language representation of the requirements
|
156
|
+
def requirements_description
|
157
|
+
@requirement.map {|req| "'#{req}'" }.join(', ')
|
158
|
+
end
|
159
|
+
|
160
|
+
# If strict? is true, only attempt the first matching executable on the path
|
161
|
+
# @api private
|
162
|
+
# @return [Boolean]
|
163
|
+
def strict?
|
164
|
+
false | @strict
|
165
|
+
end
|
166
|
+
|
167
|
+
# Given a path to an executable, detect its version
|
168
|
+
# @api private
|
169
|
+
# @param executable_path [String]
|
170
|
+
# @return [String]
|
171
|
+
# @raise [ArgumentError] if version cannot be detected.
|
172
|
+
def detect_version(executable_path)
|
173
|
+
# No need to shell out if we are only checking its presence.
|
174
|
+
return '99.version_detection_not_required' unless @requirement
|
175
|
+
|
176
|
+
raw_version = @detector.to_proc.call(executable_path)
|
177
|
+
raw_version || raise(ArgumentError,
|
178
|
+
"The detector #{@detector} failed to detect the" +
|
179
|
+
"version of the executable at '#{executable_path}'")
|
180
|
+
end
|
181
|
+
|
182
|
+
# Analog of Windows `where` command, or a `which` that finds *all*
|
183
|
+
# matching executables on the supplied path.
|
184
|
+
# @return [Enumerable<String>] - the executables found, lazily.
|
185
|
+
def find_executables
|
186
|
+
return enum_for(:find_executables) unless block_given?
|
187
|
+
|
188
|
+
exts = ENV.has_key?('PATHEXT') ? ENV.fetch('PATHEXT').split(';') : ['']
|
189
|
+
paths = @path.sub('*', ENV['PATH']).split(File::PATH_SEPARATOR)
|
190
|
+
cmds = strict? ? @executables.first(1) : @executables
|
191
|
+
|
192
|
+
cmds.product(paths, exts).map do |cmd, path, ext|
|
193
|
+
exe = File.join(path, "#{cmd}#{ext}")
|
194
|
+
yield exe if File.executable?(exe)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
data/lib/cliver/detector.rb
CHANGED
@@ -5,9 +5,16 @@ module Cliver
|
|
5
5
|
# Default implementation of the detector needed by Cliver::Assertion,
|
6
6
|
# which will take anything that #respond_to?(:to_proc)
|
7
7
|
class Detector < Struct.new(:command_arg, :version_pattern)
|
8
|
+
# @param detector_argument [#call, Object]
|
9
|
+
# If detector_argument responds to #call, return it; otherwise attempt
|
10
|
+
# to create an instance of self.
|
11
|
+
def self.generate(detector_argument)
|
12
|
+
return detector_argument if detector_argument.respond_to?(:call)
|
13
|
+
new(*Array(detector_argument))
|
14
|
+
end
|
8
15
|
|
9
16
|
# Default pattern to use when searching {#version_command} output
|
10
|
-
DEFAULT_VERSION_PATTERN = /version [0-9][.0-9a-z]+/i.freeze
|
17
|
+
DEFAULT_VERSION_PATTERN = /(version ?)?[0-9][.0-9a-z]+/i.freeze
|
11
18
|
|
12
19
|
# Default command argument to use against the executable to get
|
13
20
|
# version output
|
@@ -15,22 +22,25 @@ module Cliver
|
|
15
22
|
|
16
23
|
# Forgiving input, allows either argument if only one supplied.
|
17
24
|
#
|
18
|
-
# @overload initialize(
|
25
|
+
# @overload initialize(*command_args)
|
26
|
+
# @param command_args [Array<String>]
|
19
27
|
# @overload initialize(version_pattern)
|
20
|
-
#
|
21
|
-
# @
|
22
|
-
#
|
28
|
+
# @param version_pattern [Regexp]
|
29
|
+
# @overload initialize(*command_args, version_pattern)
|
30
|
+
# @param command_args [Array<String>]
|
31
|
+
# @param version_pattern [Regexp]
|
23
32
|
def initialize(*args)
|
24
|
-
|
25
|
-
|
26
|
-
|
33
|
+
version_pattern = args.pop if args.last.kind_of?(Regexp)
|
34
|
+
command_args = args unless args.empty?
|
35
|
+
|
36
|
+
super(command_args, version_pattern)
|
27
37
|
end
|
28
38
|
|
29
39
|
# @param executable_path [String] - the path to the executable to test
|
30
40
|
# @return [String] - should be contain {Gem::Version}-parsable
|
31
41
|
# version number.
|
32
42
|
def detect_version(executable_path)
|
33
|
-
output =
|
43
|
+
output = shell_out_and_capture version_command(executable_path).shelljoin
|
34
44
|
output[version_pattern]
|
35
45
|
end
|
36
46
|
|
@@ -53,7 +63,7 @@ module Cliver
|
|
53
63
|
|
54
64
|
# The argument to pass to the executable to get current version
|
55
65
|
# Defaults to {DEFAULT_COMMAND_ARG}
|
56
|
-
# @return [String]
|
66
|
+
# @return [String, Array<String>]
|
57
67
|
def command_arg
|
58
68
|
super || DEFAULT_COMMAND_ARG
|
59
69
|
end
|
@@ -61,7 +71,15 @@ module Cliver
|
|
61
71
|
# @param executable_path [String] the executable to test
|
62
72
|
# @return [Array<String>]
|
63
73
|
def version_command(executable_path)
|
64
|
-
[executable_path, command_arg]
|
74
|
+
[executable_path, *Array(command_arg)]
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# @api private
|
80
|
+
# A boundary that is useful for testing.
|
81
|
+
def shell_out_and_capture(command)
|
82
|
+
`#{command} 2>&1`
|
65
83
|
end
|
66
84
|
end
|
67
85
|
end
|
data/lib/cliver/filter.rb
CHANGED
@@ -6,13 +6,23 @@ module Cliver
|
|
6
6
|
# The identity filter returns its input unchanged.
|
7
7
|
IDENTITY = proc { |version| version }
|
8
8
|
|
9
|
+
# Apply to a list of requirements
|
10
|
+
# @param requirements [Array<String>]
|
11
|
+
# @return [Array<String>]
|
9
12
|
def requirements(requirements)
|
10
13
|
requirements.map do |requirement|
|
11
14
|
req_parts = requirement.split(/\b(?=\d)/, 2)
|
12
15
|
version = req_parts.last
|
13
|
-
version.replace
|
16
|
+
version.replace apply(version)
|
14
17
|
req_parts.join
|
15
18
|
end
|
16
19
|
end
|
20
|
+
|
21
|
+
# Apply to some input
|
22
|
+
# @param version [String]
|
23
|
+
# @return [String]
|
24
|
+
def apply(version)
|
25
|
+
to_proc.call(version)
|
26
|
+
end
|
17
27
|
end
|
18
28
|
end
|
data/lib/cliver/version.rb
CHANGED
@@ -21,7 +21,7 @@ describe Cliver::Detector do
|
|
21
21
|
let(:version_arg) { '--release-version' }
|
22
22
|
let(:args) { [version_arg] }
|
23
23
|
|
24
|
-
its(:command_arg) { should eq version_arg }
|
24
|
+
its(:command_arg) { should eq [version_arg] }
|
25
25
|
its(:version_pattern) { should eq defaults[:version_pattern] }
|
26
26
|
end
|
27
27
|
|
@@ -38,7 +38,7 @@ describe Cliver::Detector do
|
|
38
38
|
let(:regexp_arg) { /.*/ }
|
39
39
|
let(:args) { [version_arg, regexp_arg] }
|
40
40
|
|
41
|
-
its(:command_arg) { should eq version_arg }
|
41
|
+
its(:command_arg) { should eq [version_arg] }
|
42
42
|
its(:version_pattern) { should eq regexp_arg }
|
43
43
|
end
|
44
44
|
end
|
data/spec/cliver_spec.rb
CHANGED
@@ -1,31 +1,203 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'cliver'
|
3
|
+
require 'spec_helper'
|
3
4
|
|
4
5
|
describe Cliver do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
# The setup. Your test will likeley interact with subject.
|
7
|
+
let(:action) { Cliver.public_send(method, *args, &block) }
|
8
|
+
subject { action }
|
9
|
+
|
10
|
+
# These can get overridden in context blocks
|
11
|
+
let(:method) { raise ArgumentError, 'spec didn\'t specify :method' }
|
12
|
+
let(:args) { raise ArgumentError, 'spec didn\'t specify :args' }
|
13
|
+
let(:block) { version_map.method(:fetch) }
|
14
|
+
|
15
|
+
before(:each) do
|
16
|
+
File.stub(:executable?) { |path| executables.include? path }
|
17
|
+
# Cliver::Dependency.any_instance.stub(:detect_version) do |arg|
|
18
|
+
# version_map[arg]
|
19
|
+
# end
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:options) do
|
23
|
+
{
|
24
|
+
:path => path,
|
25
|
+
:executable => executable,
|
26
|
+
}
|
27
|
+
end
|
28
|
+
let(:executables) { version_map.keys }
|
29
|
+
let(:args) do
|
30
|
+
args = [Array(executable)]
|
31
|
+
args.concat Array(requirement)
|
32
|
+
args << options
|
33
|
+
end
|
34
|
+
|
35
|
+
let(:path) { 'foo/bar:baz/bingo' }
|
36
|
+
let(:executable) { 'doodle' }
|
37
|
+
let(:requirement) { '~>1.1'}
|
38
|
+
|
39
|
+
context 'when first-found version is sufficient' do
|
40
|
+
|
41
|
+
let(:version_map) do
|
42
|
+
{'baz/bingo/doodle' => '1.2.1'}
|
43
|
+
end
|
44
|
+
|
45
|
+
context '::assert' do
|
46
|
+
let(:method) { :assert }
|
47
|
+
it 'should not raise' do
|
48
|
+
expect { action }.to_not raise_exception
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context '::dependency_unmet?' do
|
53
|
+
let(:method) { :dependency_unmet? }
|
14
54
|
it { should be_false }
|
15
55
|
end
|
16
|
-
context '
|
17
|
-
let(:
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
it
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
56
|
+
context '::detect' do
|
57
|
+
let(:method) { :detect }
|
58
|
+
it { should eq 'baz/bingo/doodle' }
|
59
|
+
end
|
60
|
+
context '::detect!' do
|
61
|
+
let(:method) { :detect! }
|
62
|
+
it 'should not raise' do
|
63
|
+
expect { action }.to_not raise_exception
|
64
|
+
end
|
65
|
+
it { should eq 'baz/bingo/doodle' }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'when first-found version insufficient' do
|
70
|
+
let(:version_map) do
|
71
|
+
{'baz/bingo/doodle' => '1.0.1'}
|
72
|
+
end
|
73
|
+
context '::assert' do
|
74
|
+
let(:method) { :assert }
|
75
|
+
it 'should raise' do
|
76
|
+
expect { action }.to raise_exception Cliver::Dependency::VersionMismatch
|
77
|
+
end
|
78
|
+
end
|
79
|
+
context '::dependency_unmet?' do
|
80
|
+
let(:method) { :dependency_unmet? }
|
81
|
+
it { should be_true }
|
82
|
+
end
|
83
|
+
context '::detect' do
|
84
|
+
let(:method) { :detect }
|
85
|
+
it { should be_nil }
|
86
|
+
end
|
87
|
+
context '::detect!' do
|
88
|
+
let(:method) { :detect! }
|
89
|
+
it 'should not raise' do
|
90
|
+
expect { action }.to raise_exception Cliver::Dependency::VersionMismatch
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'and when sufficient version found later on path' do
|
95
|
+
let(:version_map) do
|
96
|
+
{
|
97
|
+
'foo/bar/doodle' => '0.0.1',
|
98
|
+
'baz/bingo/doodle' => '1.1.0',
|
99
|
+
}
|
100
|
+
end
|
101
|
+
context '::assert' do
|
102
|
+
let(:method) { :assert }
|
103
|
+
it 'should raise' do
|
104
|
+
expect { action }.to raise_exception Cliver::Dependency::VersionMismatch
|
105
|
+
end
|
106
|
+
end
|
107
|
+
context '::dependency_unmet?' do
|
108
|
+
let(:method) { :dependency_unmet? }
|
109
|
+
it { should be_true }
|
110
|
+
end
|
111
|
+
context '::detect' do
|
112
|
+
let(:method) { :detect }
|
113
|
+
it { should eq 'baz/bingo/doodle'}
|
114
|
+
end
|
115
|
+
context '::detect!' do
|
116
|
+
let(:method) { :detect! }
|
117
|
+
it 'should not raise' do
|
118
|
+
expect { action }.to_not raise_exception
|
119
|
+
end
|
120
|
+
it { should eq 'baz/bingo/doodle' }
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context 'when no found version' do
|
126
|
+
let(:version_map) { {} }
|
127
|
+
|
128
|
+
context '::assert' do
|
129
|
+
let(:method) { :assert }
|
130
|
+
it 'should raise' do
|
131
|
+
expect { action }.to raise_exception Cliver::Dependency::NotFound
|
132
|
+
end
|
133
|
+
end
|
134
|
+
context '::dependency_unmet?' do
|
135
|
+
let(:method) { :dependency_unmet? }
|
136
|
+
it { should be_true }
|
137
|
+
end
|
138
|
+
context '::detect' do
|
139
|
+
let(:method) { :detect }
|
140
|
+
it { should be_nil }
|
141
|
+
end
|
142
|
+
context '::detect!' do
|
143
|
+
let(:method) { :detect! }
|
144
|
+
it 'should not raise' do
|
145
|
+
expect { action }.to raise_exception Cliver::Dependency::NotFound
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context 'with fallback executable names' do
|
151
|
+
let(:executable) { ['primary', 'fallback'] }
|
152
|
+
let(:requirement) { '~> 1.1' }
|
153
|
+
context 'when primary exists after secondary in path' do
|
154
|
+
context 'and primary sufficient' do
|
155
|
+
let(:version_map) do
|
156
|
+
{
|
157
|
+
'baz/bingo/primary' => '1.1',
|
158
|
+
'foo/bar/fallback' => '1.1'
|
159
|
+
}
|
160
|
+
end
|
161
|
+
context '::detect' do
|
162
|
+
let(:method) { :detect }
|
163
|
+
it { should eq 'baz/bingo/primary' }
|
164
|
+
end
|
165
|
+
end
|
166
|
+
context 'and primary insufficient' do
|
167
|
+
let(:version_map) do
|
168
|
+
{
|
169
|
+
'baz/bingo/primary' => '2.1',
|
170
|
+
'foo/bar/fallback' => '1.1'
|
171
|
+
}
|
172
|
+
end
|
173
|
+
context 'the secondary' do
|
174
|
+
context '::detect' do
|
175
|
+
let(:method) { :detect }
|
176
|
+
it { should eq 'foo/bar/fallback' }
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
context 'when primary does not exist in path' do
|
182
|
+
context 'and sufficient secondary does' do
|
183
|
+
let(:version_map) do
|
184
|
+
{
|
185
|
+
'foo/bar/fallback' => '1.1'
|
186
|
+
}
|
187
|
+
end
|
188
|
+
context '::detect' do
|
189
|
+
let(:method) { :detect }
|
190
|
+
it { should eq 'foo/bar/fallback' }
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
context 'neither found' do
|
196
|
+
context '::detect' do
|
197
|
+
let(:version_map) { {} }
|
198
|
+
let(:method) { :detect }
|
199
|
+
it { should be_nil }
|
200
|
+
end
|
29
201
|
end
|
30
202
|
end
|
31
203
|
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# 1.8.x doesn't support public_send and we use it in spec,
|
4
|
+
# so we emulate it in this monkeypatch.
|
5
|
+
class Object
|
6
|
+
def public_send(method, *args, &block)
|
7
|
+
case method.to_s
|
8
|
+
when *private_methods
|
9
|
+
raise NoMethodError, "private method `#{method}' called for #{self}"
|
10
|
+
when *protected_methods
|
11
|
+
raise NoMethodError, "protected method `#{method}' called for #{self}"
|
12
|
+
else
|
13
|
+
send(method, *args, &block)
|
14
|
+
end
|
15
|
+
end unless method_defined?(:public_send)
|
16
|
+
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.2.0
|
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-07-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -124,16 +124,13 @@ files:
|
|
124
124
|
- Rakefile
|
125
125
|
- cliver.gemspec
|
126
126
|
- lib/cliver.rb
|
127
|
-
- lib/cliver/
|
127
|
+
- lib/cliver/dependency.rb
|
128
128
|
- lib/cliver/detector.rb
|
129
129
|
- lib/cliver/filter.rb
|
130
130
|
- lib/cliver/version.rb
|
131
|
-
- lib/cliver/which.rb
|
132
|
-
- lib/cliver/which/posix.rb
|
133
|
-
- lib/cliver/which/windows.rb
|
134
|
-
- spec/cliver/assertion_spec.rb
|
135
131
|
- spec/cliver/detector_spec.rb
|
136
132
|
- spec/cliver_spec.rb
|
133
|
+
- spec/spec_helper.rb
|
137
134
|
homepage: https://www.github.com/yaauie/cliver
|
138
135
|
licenses:
|
139
136
|
- MIT
|
@@ -160,7 +157,7 @@ signing_key:
|
|
160
157
|
specification_version: 3
|
161
158
|
summary: Cross-platform version constraints for cli tools
|
162
159
|
test_files:
|
163
|
-
- spec/cliver/assertion_spec.rb
|
164
160
|
- spec/cliver/detector_spec.rb
|
165
161
|
- spec/cliver_spec.rb
|
162
|
+
- spec/spec_helper.rb
|
166
163
|
has_rdoc: yard
|
data/lib/cliver/assertion.rb
DELETED
@@ -1,89 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
require 'open3'
|
3
|
-
require 'rubygems/requirement'
|
4
|
-
|
5
|
-
module Cliver
|
6
|
-
# The core of Cliver, Assertion is responsible for detecting the
|
7
|
-
# installed version of a binary and determining if it meets the requirements
|
8
|
-
class Assertion
|
9
|
-
|
10
|
-
include Which # platform-specific implementation of `which`
|
11
|
-
|
12
|
-
# An exception class raised when assertion is not met
|
13
|
-
DependencyNotMet = Class.new(ArgumentError)
|
14
|
-
|
15
|
-
# An exception that is raised when executable present is the wrong version
|
16
|
-
DependencyVersionMismatch = Class.new(DependencyNotMet)
|
17
|
-
|
18
|
-
# An exception that is raised when executable is not present
|
19
|
-
DependencyNotFound = Class.new(DependencyNotMet)
|
20
|
-
|
21
|
-
# A pattern for extracting a {Gem::Version}-parsable version
|
22
|
-
PARSABLE_GEM_VERSION = /[0-9]+(.[0-9]+){0,4}(.[a-zA-Z0-9]+)?/.freeze
|
23
|
-
|
24
|
-
# @overload initialize(executable, *requirements, options = {})
|
25
|
-
# @param executable [String]
|
26
|
-
# @param requirements [Array<String>, String] splat of strings
|
27
|
-
# whose elements follow the pattern
|
28
|
-
# [<operator>] <version>
|
29
|
-
# Where <operator> is optional (default '='') and in the set
|
30
|
-
# '=', '!=', '>', '<', '>=', '<=', or '~>'
|
31
|
-
# And <version> is dot-separated integers with optional
|
32
|
-
# alphanumeric pre-release suffix. See also
|
33
|
-
# {http://docs.rubygems.org/read/chapter/16 Specifying Versions}
|
34
|
-
# @param options [Hash<Symbol,Object>]
|
35
|
-
# @option options [Cliver::Detector, #to_proc] :detector (Detector.new)
|
36
|
-
# @option options [#to_proc] :filter ({Cliver::Filter::IDENTITY})
|
37
|
-
# @yieldparam [String] full path to executable
|
38
|
-
# @yieldreturn [String] containing a {Gem::Version}-parsable substring
|
39
|
-
def initialize(executable, *args, &detector)
|
40
|
-
options = args.last.kind_of?(Hash) ? args.pop : {}
|
41
|
-
@detector = detector || options.fetch(:detector) { Detector.new }
|
42
|
-
@filter = options.fetch(:filter, Filter::IDENTITY).extend(Filter)
|
43
|
-
|
44
|
-
@executable = executable.dup.freeze
|
45
|
-
|
46
|
-
unless args.empty?
|
47
|
-
@requirement = Gem::Requirement.new(@filter.requirements(args))
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
# @raise [DependencyVersionMismatch] if installed version does not match
|
52
|
-
# @raise [DependencyNotFound] if no installed version on your path
|
53
|
-
def assert!
|
54
|
-
version = installed_version ||
|
55
|
-
raise(DependencyNotFound,
|
56
|
-
"required command-line executable '#{@executable}' " +
|
57
|
-
'could not be found on your PATH.')
|
58
|
-
|
59
|
-
if @requirement && !@requirement.satisfied_by?(Gem::Version.new(version))
|
60
|
-
raise DependencyVersionMismatch,
|
61
|
-
"expected command-line executable '#{@executable}' to " +
|
62
|
-
"be #{@requirement}, got #{version}"
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
# Finds the executable on your path using {Cliver::Which};
|
67
|
-
# if the executable is present and version requirements are specified,
|
68
|
-
# uses the specified detector to get the current version.
|
69
|
-
# @private
|
70
|
-
# @return [nil] if no version present
|
71
|
-
# @return [String] Gem::Version-parsable string version
|
72
|
-
# @return [true] if present and no requirements (optimization)
|
73
|
-
def installed_version
|
74
|
-
executable_path = which(@executable)
|
75
|
-
return nil unless executable_path
|
76
|
-
return true unless @requirement
|
77
|
-
|
78
|
-
version_string = @detector.to_proc.call(executable_path)
|
79
|
-
version_string &&= @filter.to_proc.call(version_string)
|
80
|
-
(version_string && version_string[PARSABLE_GEM_VERSION]).tap do |version|
|
81
|
-
unless version
|
82
|
-
raise ArgumentError,
|
83
|
-
"found command-line executable #{@executable} at " +
|
84
|
-
"'#{executable_path}' but could not detect its version."
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
data/lib/cliver/which.rb
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
module Cliver
|
4
|
-
# The `which` command we love on many posix-systems needs analogues on other
|
5
|
-
# systems. The Which module congitionally includes the correct implementation
|
6
|
-
# into itself, so you can include it into something else.
|
7
|
-
module Which
|
8
|
-
case RbConfig::CONFIG['host_os']
|
9
|
-
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
10
|
-
require 'cliver/which/windows'
|
11
|
-
include Cliver::Which::Windows
|
12
|
-
else
|
13
|
-
require 'cliver/which/posix'
|
14
|
-
include Cliver::Which::Posix
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
data/lib/cliver/which/posix.rb
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
require 'shellwords'
|
3
|
-
|
4
|
-
module Cliver
|
5
|
-
module Which
|
6
|
-
# Posix implementation of Which
|
7
|
-
# Required and mixed into Cliver::Which in posix environments
|
8
|
-
module Posix
|
9
|
-
# Posix adapter to `which`
|
10
|
-
# @param executable [String]
|
11
|
-
# @return [nil,String] - path to found executable
|
12
|
-
def which(executable)
|
13
|
-
which = `which #{Shellwords.escape executable} 2>&1`
|
14
|
-
executable_path = which.chomp
|
15
|
-
return nil if executable_path.empty?
|
16
|
-
executable_path
|
17
|
-
rescue Errno::ENOENT
|
18
|
-
raise '"which" must be on your path to use Cliver on this system.'
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
data/lib/cliver/which/windows.rb
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
require 'shellwords'
|
3
|
-
|
4
|
-
module Cliver
|
5
|
-
module Which
|
6
|
-
# Windows-specific implementation of Which
|
7
|
-
# Required and mixed into Cliver::Which in windows environments
|
8
|
-
module Windows
|
9
|
-
# Windows-specific implementation of `which`
|
10
|
-
# @param executable [String]
|
11
|
-
# @return [nil,String] - path to found executable
|
12
|
-
def which(executable)
|
13
|
-
# `where` returns newline-separated files found on path, but doesn't
|
14
|
-
# ensure that they are executable as commands.
|
15
|
-
where = `where #{Shellwords.escape executable} 2>&1`
|
16
|
-
where.lines.map(&:chomp).find do |found|
|
17
|
-
next if found.empty?
|
18
|
-
File.executable?(found)
|
19
|
-
end
|
20
|
-
rescue Errno::ENOENT
|
21
|
-
raise '"where" must be on your path to use Cliver on Windows.'
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
@@ -1,156 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
require 'cliver'
|
3
|
-
|
4
|
-
describe Cliver::Assertion do
|
5
|
-
let(:mismatch_exception) { Cliver::Assertion::DependencyVersionMismatch }
|
6
|
-
let(:missing_exception) { Cliver::Assertion::DependencyNotFound }
|
7
|
-
let(:requirements) { ['6.8'] }
|
8
|
-
let(:executable) { 'fubar' }
|
9
|
-
let(:detector) { nil }
|
10
|
-
let(:assertion) do
|
11
|
-
Cliver::Assertion.new(executable, *requirements, &detector)
|
12
|
-
end
|
13
|
-
|
14
|
-
context 'when dependency found' do
|
15
|
-
before(:each) { assertion.stub(:installed_version) { version } }
|
16
|
-
|
17
|
-
# sampling of requirements; actual implementation
|
18
|
-
# is supplied by rubygems/requirement and well-tested there.
|
19
|
-
context '~>' do
|
20
|
-
let(:requirements) { ['~> 6.8'] }
|
21
|
-
context 'when version matches exactly' do
|
22
|
-
let(:version) { '6.8' }
|
23
|
-
it 'should not raise' do
|
24
|
-
expect { assertion.assert! }.to_not raise_exception
|
25
|
-
end
|
26
|
-
end
|
27
|
-
context 'when major matches, and minor too low' do
|
28
|
-
let(:version) { '6.7' }
|
29
|
-
it 'should raise' do
|
30
|
-
expect { assertion.assert! }.to raise_exception mismatch_exception
|
31
|
-
end
|
32
|
-
end
|
33
|
-
context 'when major matches, and minor bumped' do
|
34
|
-
let(:version) { '6.13' }
|
35
|
-
it 'should not raise' do
|
36
|
-
expect { assertion.assert! }.to_not raise_exception
|
37
|
-
end
|
38
|
-
end
|
39
|
-
context 'when major too high' do
|
40
|
-
let(:version) { '7.0' }
|
41
|
-
it 'should raise' do
|
42
|
-
expect { assertion.assert! }.to raise_exception mismatch_exception
|
43
|
-
end
|
44
|
-
end
|
45
|
-
context 'patch version present' do
|
46
|
-
let(:version) { '6.8.1' }
|
47
|
-
it 'should not raise' do
|
48
|
-
expect { assertion.assert! }.to_not raise_exception
|
49
|
-
end
|
50
|
-
end
|
51
|
-
context 'pre-release of version that matches' do
|
52
|
-
let(:version) { '6.8.a' }
|
53
|
-
it 'should raise' do
|
54
|
-
expect { assertion.assert! }.to raise_exception mismatch_exception
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
context 'multi [>=,<]' do
|
60
|
-
let(:requirements) { ['>= 1.1.4', '< 3.1'] }
|
61
|
-
context 'matches both' do
|
62
|
-
let(:version) { '2.0' }
|
63
|
-
it 'should not raise' do
|
64
|
-
expect { assertion.assert! }.to_not raise_exception
|
65
|
-
end
|
66
|
-
end
|
67
|
-
context 'fails one' do
|
68
|
-
let(:version) { '3.1' }
|
69
|
-
it 'should raise' do
|
70
|
-
expect { assertion.assert! }.to raise_exception mismatch_exception
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
context 'none' do
|
76
|
-
let(:requirements) { [] }
|
77
|
-
let(:version) { '3.1' }
|
78
|
-
it 'should not raise' do
|
79
|
-
expect { assertion.assert! }.to_not raise_exception
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
context 'when dependency not found' do
|
85
|
-
before(:each) { assertion.stub(:installed_version) { nil } }
|
86
|
-
|
87
|
-
it 'should raise' do
|
88
|
-
expect { assertion.assert! }.to raise_exception missing_exception
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
context '#installed_version' do
|
93
|
-
before(:each) do
|
94
|
-
if `which #{executable}`.chomp.empty?
|
95
|
-
pending "#{executable} not installed, test will flap."
|
96
|
-
end
|
97
|
-
end
|
98
|
-
let(:detector_touches) { [] }
|
99
|
-
context 'ruby using filter' do
|
100
|
-
let(:requirement) { '~> 1.2.3p112' }
|
101
|
-
let(:executable) { 'ruby' }
|
102
|
-
let(:filter) { proc { |ver| ver.tr('p', '.') } }
|
103
|
-
let(:detector) { proc { '1.2.3p456' } }
|
104
|
-
let(:assertion) do
|
105
|
-
Cliver::Assertion.new(executable, requirement, :filter => filter,
|
106
|
-
:detector => detector)
|
107
|
-
end
|
108
|
-
let(:installed_version) { assertion.installed_version }
|
109
|
-
subject { installed_version }
|
110
|
-
|
111
|
-
it { should eq '1.2.3.456' }
|
112
|
-
end
|
113
|
-
context 'ruby with detector-block returned value' do
|
114
|
-
let(:requirements) { ['~> 10.1.4'] }
|
115
|
-
let(:fake_version) { 'ruby 10.1.5' }
|
116
|
-
let(:executable) { 'ruby' }
|
117
|
-
let(:detector) do
|
118
|
-
proc do |ruby|
|
119
|
-
detector_touches << true
|
120
|
-
fake_version
|
121
|
-
end
|
122
|
-
end
|
123
|
-
it 'should succeed' do
|
124
|
-
expect { assertion.assert! }.to_not raise_exception
|
125
|
-
end
|
126
|
-
context 'the detector' do
|
127
|
-
before(:each) { assertion.assert! }
|
128
|
-
it 'should have been touched' do
|
129
|
-
detector_touches.should_not be_empty
|
130
|
-
end
|
131
|
-
end
|
132
|
-
context 'when block-return doesn\'t meet requirements' do
|
133
|
-
let(:fake_version) { '10.1433.32.alpha' }
|
134
|
-
it 'should raise' do
|
135
|
-
expect { assertion.assert! }.to raise_exception mismatch_exception
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
139
|
-
context 'awk with no requirements' do
|
140
|
-
|
141
|
-
let(:requirements) { [] }
|
142
|
-
let(:executable) { 'awk' }
|
143
|
-
let(:fake_version) { nil }
|
144
|
-
|
145
|
-
it 'should succeed' do
|
146
|
-
expect { assertion.assert! }.to_not raise_exception
|
147
|
-
end
|
148
|
-
context 'the detector' do
|
149
|
-
before(:each) { assertion.assert! }
|
150
|
-
it 'should not have been touched' do
|
151
|
-
detector_touches.should be_empty
|
152
|
-
end
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|