cliver 0.0.1 → 0.1.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/CONTRIBUTING.md +35 -0
- data/README.md +28 -10
- data/lib/cliver.rb +2 -0
- data/lib/cliver/assertion.rb +34 -21
- data/lib/cliver/detector.rb +43 -0
- data/lib/cliver/detector/default.rb +39 -0
- data/lib/cliver/version.rb +1 -1
- data/spec/cliver/assertion_spec.rb +65 -12
- data/spec/cliver/detector_spec.rb +44 -0
- metadata +6 -1
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Contributing
|
2
|
+
|
3
|
+
`Cliver` is [MIT-liennsed](LICENSE.txt) and intends to follow the
|
4
|
+
[pull-request-hack][].
|
5
|
+
|
6
|
+
## Git-Flow
|
7
|
+
|
8
|
+
`Cliver` follows the [git-flow][] branching model, which means that every
|
9
|
+
commit on `master` is a release. The default working branch is `develop`, so
|
10
|
+
in general please keep feature pull-requests based against the current
|
11
|
+
`develop`.
|
12
|
+
|
13
|
+
- fork cliver
|
14
|
+
- use the git-flow model to start your feature or hotfix
|
15
|
+
- make some commits (please include specs)
|
16
|
+
- submit a pull-request
|
17
|
+
|
18
|
+
## Bug Reporting
|
19
|
+
|
20
|
+
Please include clear steps-to-reproduce. Spec files are especially welcome;
|
21
|
+
a failing spec can be contributed as a pull-request against `develop`.
|
22
|
+
|
23
|
+
## Ruby Appraiser
|
24
|
+
|
25
|
+
`Cliver` uses the [ruby-appraiser][] gem via [pre-commit][] hook, which can be
|
26
|
+
activated by installing [icefox/git-hooks][] and running `git-hooks --install`.
|
27
|
+
Reek and Rubocop are strong guidelines; use them to reduce defects as much as
|
28
|
+
you can, but if you believe clarity will be sacrificed they can be bypassed
|
29
|
+
with the `--no-verify` flag.
|
30
|
+
|
31
|
+
[git-flow]: http://nvie.com/posts/a-successful-git-branching-model/
|
32
|
+
[pre-commit]: .githooks/pre-commit/ruby-appraiser
|
33
|
+
[ruby-appraiser]: https://github.com/simplymeasured/ruby-appraiser
|
34
|
+
[icefox/git-hooks]: https://github.com/icefox/git-hooks
|
35
|
+
[pull-request-hack]: http://felixge.de/2013/03/11/the-pull-request-hack.html
|
data/README.md
CHANGED
@@ -19,27 +19,45 @@ Cliver.assert('racc', '>= 1.0', '< 1.4.9') # many version requirements
|
|
19
19
|
|
20
20
|
If the executable can't be found on your path at all, a
|
21
21
|
`Cliver::Assertion::DependencyNotFound` exception is raised; if the version
|
22
|
-
reached does not meet the requirements, a `Cliver::Assertion::
|
23
|
-
exception is raised
|
22
|
+
reached does not meet the requirements, a `Cliver::Assertion::DependencyVersionMismatch`
|
23
|
+
exception is raised; both inherit from `Cliver::Assertion::DependencyNotMet`
|
24
24
|
|
25
25
|
## Advanced Usage:
|
26
26
|
|
27
27
|
Some programs don't provide nice 'version 1.2.3' strings in their `--version`
|
28
|
-
output; `Cliver` lets you provide your own
|
29
|
-
string version.
|
28
|
+
output; `Cliver` lets you provide your own version detector with a pattern.
|
30
29
|
|
31
30
|
```ruby
|
32
|
-
Cliver.assert('python', '~> 1.7',
|
31
|
+
Cliver.assert('python', '~> 1.7',
|
32
|
+
detector: Cliver::Detector.new(/(?<=Python )[0-9][.0-9a-z]+/))
|
33
33
|
```
|
34
34
|
|
35
|
-
Other programs don't provide a standard `--version`; `Cliver`
|
36
|
-
provide your own arg:
|
35
|
+
Other programs don't provide a standard `--version`; `Cliver::Detector` also
|
36
|
+
allows you to provide your own arg to get the version:
|
37
37
|
|
38
38
|
```ruby
|
39
|
-
Cliver.assert('janky', '~> 10.1.alpha',
|
39
|
+
Cliver.assert('janky', '~> 10.1.alpha',
|
40
|
+
detector: Cliver::Detector.new('--release-version'))
|
40
41
|
```
|
41
42
|
|
42
|
-
|
43
|
-
|
43
|
+
Alternatively, you can supply your own detector (anything that responds to
|
44
|
+
`#to_proc`) in the options hash or as a block, so long as it returns a
|
45
|
+
`Gem::Version`-parsable version number; if it returns nil or false when
|
46
|
+
version requirements are given, a descriptive `ArgumentError` is raised.
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
Cliver.assert('oddball', '~> 10.1.alpha') do |oddball_path|
|
50
|
+
File.read(File.expand_path('../VERSION', oddball_path)).chomp
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
Since `Cliver` uses `Gem::Requirement` for version comparrisons, it obeys all
|
55
|
+
the same rules including pre-release semantics.
|
56
|
+
|
57
|
+
## See Also:
|
58
|
+
|
59
|
+
- [Contributing](CONTRIBUTING.md)
|
60
|
+
- [License](LICENSE.txt)
|
61
|
+
|
44
62
|
|
45
63
|
[rubygems/requirements]: https://github.com/rubygems/rubygems/blob/master/lib/rubygems/requirement.rb
|
data/lib/cliver.rb
CHANGED
data/lib/cliver/assertion.rb
CHANGED
@@ -6,15 +6,16 @@ module Cliver
|
|
6
6
|
# The core of Cliver, Assertion is responsible for detecting the
|
7
7
|
# installed version of a binary and determining if it meets the requirements
|
8
8
|
class Assertion
|
9
|
-
|
10
|
-
|
9
|
+
DependencyNotMet = Class.new(ArgumentError)
|
10
|
+
DependencyVersionMismatch = Class.new(DependencyNotMet)
|
11
|
+
DependencyNotFound = Class.new(DependencyNotMet)
|
11
12
|
|
12
13
|
EXECUTABLE_PATTERN = /\A[a-z][a-zA-Z0-9\-_]*\z/.freeze
|
13
14
|
|
14
15
|
# Creates a new instance with the args and calls #assert.
|
15
16
|
# @see #assert
|
16
|
-
def self.assert!(*args)
|
17
|
-
new(*args).assert!
|
17
|
+
def self.assert!(*args, &block)
|
18
|
+
new(*args, &block).assert!
|
18
19
|
end
|
19
20
|
|
20
21
|
# @overload initialize(executable, *requirements, options = {})
|
@@ -28,36 +29,48 @@ module Cliver
|
|
28
29
|
# alphanumeric pre-release suffix
|
29
30
|
# @see Gem::Requirement::new
|
30
31
|
# @param options [Hash<Symbol,Object>]
|
31
|
-
# @options options [#
|
32
|
-
# @
|
33
|
-
|
34
|
-
|
32
|
+
# @options options [Cliver::Detector, #to_proc] :detector
|
33
|
+
# @yieldparam [String] full path to executable
|
34
|
+
# @yieldreturn [String] Gem::Version-parsable string version
|
35
|
+
def initialize(executable, *args, &detector)
|
35
36
|
raise ArgumentError, 'executable' unless executable[EXECUTABLE_PATTERN]
|
36
37
|
|
38
|
+
options = args.last.kind_of?(Hash) ? args.pop : {}
|
39
|
+
|
37
40
|
@executable = executable.dup.freeze
|
38
|
-
@requirement = Gem::Requirement.new(args)
|
39
|
-
@
|
40
|
-
@version_matcher = options.fetch(:version_matcher,
|
41
|
-
/version ([0-9][.0-9a-z]+)/i)
|
41
|
+
@requirement = Gem::Requirement.new(args) unless args.empty?
|
42
|
+
@detector = detector || options.fetch(:detector) { Detector.new }
|
42
43
|
end
|
43
44
|
|
44
|
-
# @raise [
|
45
|
+
# @raise [DependencyVersionMismatch] if installed version does not match
|
45
46
|
# @raise [DependencyNotFound] if no installed version on your path
|
46
47
|
def assert!
|
47
48
|
version = installed_version
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
raise(DependencyNotFound, "#{@executable} missing.") unless version
|
50
|
+
|
51
|
+
if @requirement && !@requirement.satisfied_by?(Gem::Version.new(version))
|
52
|
+
raise DependencyVersionMismatch,
|
53
|
+
"expected #{@executable} to be #{@requirement}, got #{version}"
|
52
54
|
end
|
53
55
|
end
|
54
56
|
|
55
57
|
# @private
|
58
|
+
# @return [nil] if no version present
|
59
|
+
# @return [String] Gem::Version-parsable string version
|
60
|
+
# @return [true] if present and no requirements (optimization)
|
56
61
|
def installed_version
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
62
|
+
which, _ = Open3.capture2e("which #{@executable}")
|
63
|
+
executable_path = which.chomp
|
64
|
+
return nil if executable_path.empty?
|
65
|
+
return true unless @requirement
|
66
|
+
|
67
|
+
@detector.to_proc.call(executable_path).tap do |version|
|
68
|
+
unless version
|
69
|
+
raise ArgumentError,
|
70
|
+
"found #{@executable} at '#{executable_path}' " +
|
71
|
+
'but could not detect its version.'
|
72
|
+
end
|
73
|
+
end
|
61
74
|
end
|
62
75
|
end
|
63
76
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'open3'
|
3
|
+
|
4
|
+
module Cliver
|
5
|
+
# The interface for Cliver::Detector classes.
|
6
|
+
# @see Cliver::Detector::Default for reference implementation
|
7
|
+
module Detector
|
8
|
+
# Forward to default implementation
|
9
|
+
def self.new(*args, &block)
|
10
|
+
Default.new(*args, &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param executable [String] the executable to test
|
14
|
+
# @return [Array<String>]
|
15
|
+
def version_command(executable)
|
16
|
+
raise NotImplementedError unless defined? super
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Regexp] - the pattern used against the output
|
21
|
+
# of the #version_command, which should
|
22
|
+
# typically be Gem::Version-parsable.
|
23
|
+
def version_pattern
|
24
|
+
raise NotImplementedError unless defined? super
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param executable [String] - the path to the executable to test
|
29
|
+
# @return [String] - should be Gem::Version-parsable.
|
30
|
+
def detect_version(executable)
|
31
|
+
output, _ = Open3.capture2e(*version_command(executable))
|
32
|
+
ver = output.scan(version_pattern)
|
33
|
+
ver && ver.first
|
34
|
+
end
|
35
|
+
|
36
|
+
# This is the interface that any detector must have.
|
37
|
+
# @see #detect_version for the returned proc's method signature.
|
38
|
+
# @return [Proc]
|
39
|
+
def to_proc
|
40
|
+
method(:detect_version).to_proc
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'open3'
|
3
|
+
|
4
|
+
module Cliver
|
5
|
+
# Default implementation of Cliver::Detector
|
6
|
+
# Requires a command argument (default '--version')
|
7
|
+
# and a pattern-matcher Regexp with sensible default.
|
8
|
+
class Detector::Default < Struct.new(:command_arg, :version_pattern)
|
9
|
+
include Detector
|
10
|
+
|
11
|
+
DEFAULT_VERSION_PATTERN = /(?<=version )[0-9][.0-9a-z]+/i.freeze
|
12
|
+
DEFAULT_COMMAND_ARG = '--version'.freeze
|
13
|
+
|
14
|
+
# Forgiving input, allows either argument if only one supplied.
|
15
|
+
#
|
16
|
+
# @overload initialize(command_arg)
|
17
|
+
# @overload initialize(version_pattern)
|
18
|
+
# @overload initialize(command_arg, version_pattern)
|
19
|
+
# @param command_arg [String] ('--version')
|
20
|
+
# @param version_pattern [Regexp] (/(?<=version )[0-9][.0-9a-z]+/i)
|
21
|
+
def initialize(*args)
|
22
|
+
command_arg = args.shift if args.first.kind_of?(String)
|
23
|
+
version_pattern = args.shift
|
24
|
+
super(command_arg, version_pattern)
|
25
|
+
end
|
26
|
+
|
27
|
+
def version_pattern
|
28
|
+
super || DEFAULT_VERSION_PATTERN
|
29
|
+
end
|
30
|
+
|
31
|
+
def command_arg
|
32
|
+
super || DEFAULT_COMMAND_ARG
|
33
|
+
end
|
34
|
+
|
35
|
+
def version_command(executable)
|
36
|
+
[executable, command_arg]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/cliver/version.rb
CHANGED
@@ -2,11 +2,14 @@
|
|
2
2
|
require 'cliver/assertion'
|
3
3
|
|
4
4
|
describe Cliver::Assertion do
|
5
|
-
let(:mismatch_exception) { Cliver::Assertion::
|
5
|
+
let(:mismatch_exception) { Cliver::Assertion::DependencyVersionMismatch }
|
6
6
|
let(:missing_exception) { Cliver::Assertion::DependencyNotFound }
|
7
7
|
let(:requirements) { ['6.8'] }
|
8
8
|
let(:executable) { 'fubar' }
|
9
|
-
let(:
|
9
|
+
let(:detector) { nil }
|
10
|
+
let(:assertion) do
|
11
|
+
Cliver::Assertion.new(executable, *requirements, &detector)
|
12
|
+
end
|
10
13
|
|
11
14
|
context 'when dependency found' do
|
12
15
|
before(:each) { assertion.stub(:installed_version) { version } }
|
@@ -16,39 +19,38 @@ describe Cliver::Assertion do
|
|
16
19
|
context '~>' do
|
17
20
|
let(:requirements) { ['~> 6.8'] }
|
18
21
|
context 'when version matches exactly' do
|
19
|
-
let(:version) {
|
22
|
+
let(:version) { '6.8' }
|
20
23
|
it 'should not raise' do
|
21
24
|
expect { assertion.assert! }.to_not raise_exception
|
22
25
|
end
|
23
26
|
end
|
24
27
|
context 'when major matches, and minor too low' do
|
25
|
-
let(:version) {
|
28
|
+
let(:version) { '6.7' }
|
26
29
|
it 'should raise' do
|
27
30
|
expect { assertion.assert! }.to raise_exception mismatch_exception
|
28
31
|
end
|
29
32
|
end
|
30
33
|
context 'when major matches, and minor bumped' do
|
31
|
-
let(:version) {
|
34
|
+
let(:version) { '6.13' }
|
32
35
|
it 'should not raise' do
|
33
36
|
expect { assertion.assert! }.to_not raise_exception
|
34
37
|
end
|
35
38
|
end
|
36
39
|
context 'when major too high' do
|
37
|
-
let(:version) {
|
40
|
+
let(:version) { '7.0' }
|
38
41
|
it 'should raise' do
|
39
42
|
expect { assertion.assert! }.to raise_exception mismatch_exception
|
40
43
|
end
|
41
44
|
end
|
42
45
|
context 'patch version present' do
|
43
|
-
let(:version) {
|
46
|
+
let(:version) { '6.8.1' }
|
44
47
|
it 'should not raise' do
|
45
48
|
expect { assertion.assert! }.to_not raise_exception
|
46
49
|
end
|
47
50
|
end
|
48
51
|
context 'pre-release of version that matches' do
|
49
|
-
let(:version) {
|
52
|
+
let(:version) { '6.8.a' }
|
50
53
|
it 'should raise' do
|
51
|
-
version.should be_prerelease
|
52
54
|
expect { assertion.assert! }.to raise_exception mismatch_exception
|
53
55
|
end
|
54
56
|
end
|
@@ -57,13 +59,13 @@ describe Cliver::Assertion do
|
|
57
59
|
context 'multi [>=,<]' do
|
58
60
|
let(:requirements) { ['>= 1.1.4', '< 3.1'] }
|
59
61
|
context 'matches both' do
|
60
|
-
let(:version) {
|
62
|
+
let(:version) { '2.0' }
|
61
63
|
it 'should not raise' do
|
62
64
|
expect { assertion.assert! }.to_not raise_exception
|
63
65
|
end
|
64
66
|
end
|
65
67
|
context 'fails one' do
|
66
|
-
let(:version) {
|
68
|
+
let(:version) { '3.1' }
|
67
69
|
it 'should raise' do
|
68
70
|
expect { assertion.assert! }.to raise_exception mismatch_exception
|
69
71
|
end
|
@@ -72,7 +74,7 @@ describe Cliver::Assertion do
|
|
72
74
|
|
73
75
|
context 'none' do
|
74
76
|
let(:requirements) { [] }
|
75
|
-
let(:version) {
|
77
|
+
let(:version) { '3.1' }
|
76
78
|
it 'should not raise' do
|
77
79
|
expect { assertion.assert! }.to_not raise_exception
|
78
80
|
end
|
@@ -86,4 +88,55 @@ describe Cliver::Assertion do
|
|
86
88
|
expect { assertion.assert! }.to raise_exception missing_exception
|
87
89
|
end
|
88
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 with detector-block returned value' do
|
100
|
+
let(:requirements) { ['~> 10.1.4'] }
|
101
|
+
let(:fake_version) { '10.1.5' }
|
102
|
+
let(:executable) { 'ruby' }
|
103
|
+
let(:detector) do
|
104
|
+
proc do |ruby|
|
105
|
+
detector_touches << true
|
106
|
+
fake_version
|
107
|
+
end
|
108
|
+
end
|
109
|
+
it 'should succeed' do
|
110
|
+
expect { assertion.assert! }.to_not raise_exception
|
111
|
+
end
|
112
|
+
context 'the detector' do
|
113
|
+
before(:each) { assertion.assert! }
|
114
|
+
it 'should have been touched' do
|
115
|
+
detector_touches.should_not be_empty
|
116
|
+
end
|
117
|
+
end
|
118
|
+
context 'when block-return doesn\'t meet requirements' do
|
119
|
+
let(:fake_version) { '10.1.3' }
|
120
|
+
it 'should raise' do
|
121
|
+
expect { assertion.assert! }.to raise_exception mismatch_exception
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
context 'awk with no requirements' do
|
126
|
+
|
127
|
+
let(:requirements) { [] }
|
128
|
+
let(:executable) { 'awk' }
|
129
|
+
let(:fake_version) { nil }
|
130
|
+
|
131
|
+
it 'should succeed' do
|
132
|
+
expect { assertion.assert! }.to_not raise_exception
|
133
|
+
end
|
134
|
+
context 'the detector' do
|
135
|
+
before(:each) { assertion.assert! }
|
136
|
+
it 'should not have been touched' do
|
137
|
+
detector_touches.should be_empty
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
89
142
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'cliver/detector'
|
3
|
+
|
4
|
+
describe Cliver::Detector do
|
5
|
+
let(:detector) { Cliver::Detector::Default.new(*args) }
|
6
|
+
let(:defaults) do
|
7
|
+
{
|
8
|
+
version_pattern: Cliver::Detector::Default::DEFAULT_VERSION_PATTERN,
|
9
|
+
command_arg: Cliver::Detector::Default::DEFAULT_COMMAND_ARG,
|
10
|
+
}
|
11
|
+
end
|
12
|
+
let(:args) { [] }
|
13
|
+
subject { detector }
|
14
|
+
|
15
|
+
it { should respond_to :to_proc }
|
16
|
+
|
17
|
+
its(:command_arg) { should eq defaults[:command_arg] }
|
18
|
+
its(:version_pattern) { should eq defaults[:version_pattern] }
|
19
|
+
|
20
|
+
context 'with one string argument' do
|
21
|
+
let(:version_arg) { '--release-version' }
|
22
|
+
let(:args) { [version_arg] }
|
23
|
+
|
24
|
+
its(:command_arg) { should eq version_arg }
|
25
|
+
its(:version_pattern) { should eq defaults[:version_pattern] }
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'with one regexp argument' do
|
29
|
+
let(:regexp_arg) { /.*/ }
|
30
|
+
let(:args) { [regexp_arg] }
|
31
|
+
|
32
|
+
its(:command_arg) { should eq defaults[:command_arg] }
|
33
|
+
its(:version_pattern) { should eq regexp_arg }
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'with both arguments' do
|
37
|
+
let(:version_arg) { '--release-version' }
|
38
|
+
let(:regexp_arg) { /.*/ }
|
39
|
+
let(:args) { [version_arg, regexp_arg] }
|
40
|
+
|
41
|
+
its(:command_arg) { should eq version_arg }
|
42
|
+
its(:version_pattern) { should eq regexp_arg }
|
43
|
+
end
|
44
|
+
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.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -100,6 +100,7 @@ extra_rdoc_files: []
|
|
100
100
|
files:
|
101
101
|
- .githooks/pre-commit/ruby-appraiser
|
102
102
|
- .gitignore
|
103
|
+
- CONTRIBUTING.md
|
103
104
|
- Gemfile
|
104
105
|
- LICENSE.txt
|
105
106
|
- README.md
|
@@ -107,8 +108,11 @@ files:
|
|
107
108
|
- cliver.gemspec
|
108
109
|
- lib/cliver.rb
|
109
110
|
- lib/cliver/assertion.rb
|
111
|
+
- lib/cliver/detector.rb
|
112
|
+
- lib/cliver/detector/default.rb
|
110
113
|
- lib/cliver/version.rb
|
111
114
|
- spec/cliver/assertion_spec.rb
|
115
|
+
- spec/cliver/detector_spec.rb
|
112
116
|
- spec/cliver_spec.rb
|
113
117
|
homepage: https://www.github.com/yaauie/cliver
|
114
118
|
licenses:
|
@@ -137,5 +141,6 @@ specification_version: 3
|
|
137
141
|
summary: Cross-platform version constraints for cli tools
|
138
142
|
test_files:
|
139
143
|
- spec/cliver/assertion_spec.rb
|
144
|
+
- spec/cliver/detector_spec.rb
|
140
145
|
- spec/cliver_spec.rb
|
141
146
|
has_rdoc:
|