cliver 0.1.5 → 0.2.0
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/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
|