cliver-0.3.2/0000755000004100000410000000000012576035272013041 5ustar www-datawww-datacliver-0.3.2/Rakefile0000644000004100000410000000030212576035272014501 0ustar www-datawww-data# encoding: utf-8 require 'bundler/gem_tasks' require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) do |spec| spec.pattern = FileList['spec/**/*_spec.rb'] spec.verbose = true end cliver-0.3.2/Gemfile0000644000004100000410000000015512576035272014335 0ustar www-datawww-data# encoding: utf-8 source 'https://rubygems.org' # Specify your gem's dependencies in cliver.gemspec gemspec cliver-0.3.2/LICENSE.txt0000644000004100000410000000206012576035272014662 0ustar www-datawww-dataCopyright (c) 2013 Ryan Biesemeyer MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cliver-0.3.2/spec/0000755000004100000410000000000012576035272013773 5ustar www-datawww-datacliver-0.3.2/spec/core_ext/0000755000004100000410000000000012576035272015603 5ustar www-datawww-datacliver-0.3.2/spec/core_ext/file_spec.rb0000644000004100000410000000363012576035272020063 0ustar www-datawww-data# encoding: utf-8 require 'core_ext/file' describe 'File::absolute_path?' do context 'posix' do before(:each) do stub_const("File::ALT_SEPARATOR", nil) stub_const("File::ABSOLUTE_PATH_PATTERN", File::POSIX_ABSOLUTE_PATH_PATTERN) end context 'when given an absolute path' do %w( /foo/bar /C/Windows/system32/ ).each do |path| context "(#{path})" do context 'the return value' do subject { File::absolute_path?(path) } it { should be_true } end end end end context 'when given a relative path' do %w( C:/foo/bar \\foo\\bar C:\\foo\\bar foo/bar foo ./foo/bar ../foo/bar C:foo/bar ).each do |path| context "(#{path})" do context 'the return value' do subject { File::absolute_path?(path) } it { should be_false } end end end end end context 'windows' do before(:each) do stub_const("File::ALT_SEPARATOR", '\\') stub_const("File::ABSOLUTE_PATH_PATTERN", File::WINDOWS_ABSOLUTE_PATH_PATTERN) end context 'when given an absolute path' do %w( /foo/bar C:/foo/bar \\foo\\bar C:\\foo\\bar /C/Windows/system32/ ).each do |path| context "(#{path})" do context 'the return value' do subject { File::absolute_path?(path) } it { should be_true } end end end end context 'when given a relative path' do %w( foo/bar foo ./foo/bar ../foo/bar C:foo/bar ).each do |path| context "(#{path})" do context 'the return value' do subject { File::absolute_path?(path) } it { should be_false } end end end end end end cliver-0.3.2/spec/spec_helper.rb0000644000004100000410000000076112576035272016615 0ustar www-datawww-data# encoding: utf-8 # 1.8.x doesn't support public_send and we use it in spec, # so we emulate it in this monkeypatch. class Object def public_send(method, *args, &block) case method.to_s when *private_methods raise NoMethodError, "private method `#{method}' called for #{self}" when *protected_methods raise NoMethodError, "protected method `#{method}' called for #{self}" else send(method, *args, &block) end end unless method_defined?(:public_send) end cliver-0.3.2/spec/cliver_spec.rb0000644000004100000410000002212612576035272016621 0ustar www-datawww-data# encoding: utf-8 require 'cliver' require 'spec_helper' require File.expand_path('../support/executable_mock', __FILE__) RSpec::Matchers.define :be_filesystem_equivalent_of do |expected| match do |actual| ExecutableMock.new(expected) == actual end end describe Cliver do # The setup. Your test will likeley interact with subject. let(:action) { Cliver.public_send(method, *args, &block) } subject { action } # These can get overridden in context blocks let(:method) { raise ArgumentError, 'spec didn\'t specify :method' } let(:args) { raise ArgumentError, 'spec didn\'t specify :args' } let(:block) { version_directory.method(:version) } # BecauseWindows. This enables us to mock out File::executable? # and the responses from our detectors given any representation # of a file path. let(:version_directory) do ExecutableMock::Registry.new(version_map) end before(:each) do File.stub(:executable?, &version_directory.method(:executable?)) end let(:options) do { :path => path.join(File::PATH_SEPARATOR), :executable => executable, } end let(:args) do args = [Array(executable)] args.concat Array(requirement) args << options end let(:path) { ['/foo/bar','/baz/bingo'] } let(:executable) { 'doodle' } let(:requirement) { '~>1.1'} context 'when first-found version is sufficient' do let(:version_map) do {'/baz/bingo/doodle' => '1.2.1'} end context '::assert' do let(:method) { :assert } it 'should not raise' do expect { action }.to_not raise_exception end end context '::dependency_unmet?' do let(:method) { :dependency_unmet? } it { should be_false } end context '::detect' do let(:method) { :detect } it { should be_filesystem_equivalent_of '/baz/bingo/doodle' } end context '::detect!' do let(:method) { :detect! } it 'should not raise' do expect { action }.to_not raise_exception end it { should be_filesystem_equivalent_of '/baz/bingo/doodle' } end end context '::verify!' do let(:method) { :verify! } let(:version_map) do {'/baz/bingo/doodle' => '0.2.1', '/baz/fiddle/doodle' => '1.1.4'} end let(:args) do args = [executable] args.concat Array(requirement) args << options end context 'when a relative path is given' do let(:executable) { 'foo/bar/doodle' } it 'should raise' do expect { action }.to raise_exception ArgumentError end end context 'when an absolute path is given' do context 'and that path is not found' do let(:executable) { '/blip/boom' } it 'should raise' do expect { action }.to raise_exception Cliver::Dependency::NotFound end end context '(windows path)' do before(:each) do stub_const('File::ABSOLUTE_PATH_PATTERN', File::WINDOWS_ABSOLUTE_PATH_PATTERN) end let(:version_map) do {'C:/baz/bingo/doodle.exe' => '0.2.1', 'C:/baz/fiddle/doodle.exe' => '1.1.4'} end context 'and executable at that path is sufficient' do let(:executable) { 'C:/baz/fiddle/doodle.exe' } it 'should not raise' do expect { action }.to_not raise_exception end end context 'and the executable at that path is not sufficent' do let(:executable) { 'C:/baz/bingo/doodle.exe' } it 'should raise' do expect { action }.to raise_exception Cliver::Dependency::VersionMismatch end end context 'and no executable exists at that path' do let(:version_map) { Hash.new } let(:executable) { 'C:/baz/fiddle/doodle.exe' } it 'should raise' do expect { action }.to raise_exception Cliver::Dependency::NotFound end end end context 'and the executable at that path is sufficent' do let(:executable) { '/baz/fiddle/doodle' } it 'should not raise' do expect { action }.to_not raise_exception Cliver::Dependency::NotFound end end context 'and the executable at that path is not sufficent' do let(:executable) { '/baz/bingo/doodle' } it 'should raise' do expect { action }.to raise_exception Cliver::Dependency::VersionMismatch end end end end context 'when given executable as a path' do let(:version_map) do {'/baz/bingo/doodle' => '1.2.1'} end let(:path) { ['/fiddle/foo','/deedle/dee'] } context 'that is absolute' do let(:executable) { '/baz/bingo/doodle' } %w(assert dependency_unmet? detect detect).each do |method_name| context "::#{method_name}" do let(:method) { method_name.to_sym } it 'should only detect its version once' do Cliver::Dependency.any_instance. should_receive(:detect_version). once. and_call_original action end end end end context 'that is relative' do let(:executable) { 'baz/bingo/doodle' } %w(assert dependency_unmet? detect detect).each do |method_name| context "::#{method_name}" do let(:method) { method_name.to_sym } it 'should raise an ArgumentError' do expect { action }.to raise_exception ArgumentError end end end end end context 'when first-found version insufficient' do let(:version_map) do {'/baz/bingo/doodle' => '1.0.1'} end context '::assert' do let(:method) { :assert } it 'should raise' do expect { action }.to raise_exception Cliver::Dependency::VersionMismatch end end context '::dependency_unmet?' do let(:method) { :dependency_unmet? } it { should be_true } end context '::detect' do let(:method) { :detect } it { should be_nil } end context '::detect!' do let(:method) { :detect! } it 'should not raise' do expect { action }.to raise_exception Cliver::Dependency::VersionMismatch end end context 'and when sufficient version found later on path' do let(:version_map) do { '/foo/bar/doodle' => '0.0.1', '/baz/bingo/doodle' => '1.1.0', } end context '::assert' do let(:method) { :assert } it 'should raise' do expect { action }.to raise_exception Cliver::Dependency::VersionMismatch end end context '::dependency_unmet?' do let(:method) { :dependency_unmet? } it { should be_true } end context '::detect' do let(:method) { :detect } it { should be_filesystem_equivalent_of '/baz/bingo/doodle' } end context '::detect!' do let(:method) { :detect! } it 'should not raise' do expect { action }.to_not raise_exception end it { should be_filesystem_equivalent_of '/baz/bingo/doodle' } end end end context 'when no found version' do let(:version_map) { {} } context '::assert' do let(:method) { :assert } it 'should raise' do expect { action }.to raise_exception Cliver::Dependency::NotFound end end context '::dependency_unmet?' do let(:method) { :dependency_unmet? } it { should be_true } end context '::detect' do let(:method) { :detect } it { should be_nil } end context '::detect!' do let(:method) { :detect! } it 'should not raise' do expect { action }.to raise_exception Cliver::Dependency::NotFound end end end context 'with fallback executable names' do let(:executable) { ['primary', 'fallback'] } let(:requirement) { '~> 1.1' } context 'when primary exists after secondary in path' do context 'and primary sufficient' do let(:version_map) do { '/baz/bingo/primary' => '1.1', '/foo/bar/fallback' => '1.1' } end context '::detect' do let(:method) { :detect } it { should be_filesystem_equivalent_of '/baz/bingo/primary' } end end context 'and primary insufficient' do let(:version_map) do { '/baz/bingo/primary' => '2.1', '/foo/bar/fallback' => '1.1' } end context 'the secondary' do context '::detect' do let(:method) { :detect } it { should be_filesystem_equivalent_of '/foo/bar/fallback' } end end end end context 'when primary does not exist in path' do context 'and sufficient secondary does' do let(:version_map) do { '/foo/bar/fallback' => '1.1' } end context '::detect' do let(:method) { :detect } it { should be_filesystem_equivalent_of '/foo/bar/fallback' } end end end context 'neither found' do context '::detect' do let(:version_map) { {} } let(:method) { :detect } it { should be_nil } end end end end cliver-0.3.2/spec/cliver/0000755000004100000410000000000012576035272015257 5ustar www-datawww-datacliver-0.3.2/spec/cliver/shell_capture_spec.rb0000644000004100000410000000246712576035272021461 0ustar www-datawww-data# encoding: utf-8 require 'cliver' describe Cliver::ShellCapture do let(:test_command) { 'test command' } subject { Cliver::ShellCapture.new(test_command) } context 'a command that exists' do let(:intended_stdout) { StringIO.new('1.1.1').tap(&:rewind) } let(:intended_stderr) { StringIO.new('foo baar 1').tap(&:rewind) } let(:intended_stdin) { StringIO.new('').tap(&:rewind) } ['test command', %w(test command)].each do |input| context "with #{input.class.name} input" do let(:test_command) { input } before(:each) do Open3.should_receive(:popen3) do |*args| args.size.should eq 1 args.first.should == 'test command' end.and_yield(intended_stdin, intended_stdout, intended_stderr) end its(:stdout) { should eq '1.1.1' } its(:stderr) { should eq 'foo baar 1' } its(:command_found) { should be_true } end end end context 'looking for a command that does not exist' do before(:each) do Open3.should_receive(:popen3) do |command| command.should eq test_command raise Errno::ENOENT.new("No such file or directory - #{test_command}") end end its(:stdout) { should eq '' } its(:stderr) { should eq '' } its(:command_found) { should be_false } end end cliver-0.3.2/spec/cliver/detector_spec.rb0000644000004100000410000000452112576035272020431 0ustar www-datawww-data# encoding: utf-8 require 'cliver' describe Cliver::Detector do let(:detector) { Cliver::Detector.new(*args) } let(:defaults) do { :version_pattern => Cliver::Detector::DEFAULT_VERSION_PATTERN, :command_arg => Cliver::Detector::DEFAULT_COMMAND_ARG, } end let(:args) { [] } subject { detector } it { should respond_to :to_proc } its(:command_arg) { should eq defaults[:command_arg] } its(:version_pattern) { should eq defaults[:version_pattern] } context 'with one string argument' do let(:version_arg) { '--release-version' } let(:args) { [version_arg] } its(:command_arg) { should eq [version_arg] } its(:version_pattern) { should eq defaults[:version_pattern] } end context 'with one regexp argument' do let(:regexp_arg) { /.*/ } let(:args) { [regexp_arg] } its(:command_arg) { should eq defaults[:command_arg] } its(:version_pattern) { should eq regexp_arg } end context 'with both arguments' do let(:version_arg) { '--release-version' } let(:regexp_arg) { /.*/ } let(:args) { [version_arg, regexp_arg] } its(:command_arg) { should eq [version_arg] } its(:version_pattern) { should eq regexp_arg } end context 'detecting a command' do before(:each) do Cliver::ShellCapture.stub(:new => capture) end context 'that reports version on stdout' do let(:capture) { double('capture', :stdout => '1.1', :stderr => 'Warning: There is a monkey 1.2 metres left of you.', :command_found => true) } it 'should prefer the stdout output' do expect(detector.detect_version('foo')).to eq('1.1') end end context 'that reports version on stderr' do let(:capture) { double('capture', :stdout => '', :stderr => 'Version: 1.666', :command_found => true) } it 'should prefer the stderr output' do expect(detector.detect_version('foo')).to eq('1.666') end end context 'that does not exist' do let(:capture) { Cliver::ShellCapture.new('acommandnosystemshouldhave123') } it 'should raise an exception' do expect { detector.detect_version('foo') }.to raise_error(Cliver::Dependency::NotFound) end end end end cliver-0.3.2/spec/support/0000755000004100000410000000000012576035272015507 5ustar www-datawww-datacliver-0.3.2/spec/support/executable_mock.rb0000644000004100000410000000201512576035272021164 0ustar www-datawww-data# encoding: utf-8 class ExecutableMock def initialize(path) @path = _to_platform_abs_path(path) end attr_reader :path attr_reader :version def == (other_path) other_path = _to_platform_abs_path(other_path) if other_path[/[A-Z]:/i] # windows pattern = /\A#{self.path}(#{(ENV['PATHEXT']||'').split(';').map(&Regexp::method(:escape)).join('|')})?\Z/i pattern =~ other_path else # posix self.path == other_path end end private def _to_platform_abs_path(source) (File::absolute_path?(source, :windows) && !File::absolute_path?(source, :posix)) ? source.tr('\\','/') : File.expand_path(source) end class Registry def initialize(version_map) @registry = {} version_map.each do |path,version| @registry[ExecutableMock.new(path)] = version end end def executable?(path) false | version(path) end def version(path) key = @registry.keys.find {|exe| exe == path } key && @registry[key] end end end cliver-0.3.2/.travis.yml0000644000004100000410000000022612576035272015152 0ustar www-datawww-data--- language: ruby script: "bundle exec rake spec" rvm: - 2.0.0 - 1.9.3 - jruby-19mode - rbx-19mode - 1.8.7 - jruby-18mode - rbx-18mode cliver-0.3.2/lib/0000755000004100000410000000000012576035272013607 5ustar www-datawww-datacliver-0.3.2/lib/core_ext/0000755000004100000410000000000012576035272015417 5ustar www-datawww-datacliver-0.3.2/lib/core_ext/file.rb0000644000004100000410000000244412576035272016667 0ustar www-datawww-data# encoding: utf-8 # Core-Extensions on File class File # determine whether a String path is absolute. # @example # File.absolute_path?('foo') #=> false # File.absolute_path?('/foo') #=> true # File.absolute_path?('foo/bar') #=> false # File.absolute_path?('/foo/bar') #=> true # File.absolute_path?('C:foo/bar') #=> false # File.absolute_path?('C:/foo/bar') #=> true # @param path [String] - a pathname # @return [Boolean] def self.absolute_path?(path, platform = :default) pattern = case platform when :default then ABSOLUTE_PATH_PATTERN when :windows then WINDOWS_ABSOLUTE_PATH_PATTERN when :posix then POSIX_ABSOLUTE_PATH_PATTERN else raise ArgumentError, "Unsupported platform '#{platform.inspect}'" end false | path[pattern] end unless defined?(POSIX_ABSOLUTE_PATH_PATTERN) POSIX_ABSOLUTE_PATH_PATTERN = /\A\//.freeze end unless defined?(WINDOWS_ABSOLUTE_PATH_PATTERN) WINDOWS_ABSOLUTE_PATH_PATTERN = Regexp.union( POSIX_ABSOLUTE_PATH_PATTERN, /\A([A-Z]:)?(\\|\/)/i ).freeze end ABSOLUTE_PATH_PATTERN = begin File::ALT_SEPARATOR ? WINDOWS_ABSOLUTE_PATH_PATTERN : POSIX_ABSOLUTE_PATH_PATTERN end unless defined?(ABSOLUTE_PATH_PATTERN) end cliver-0.3.2/lib/cliver/0000755000004100000410000000000012576035272015073 5ustar www-datawww-datacliver-0.3.2/lib/cliver/dependency.rb0000644000004100000410000002073112576035272017541 0ustar www-datawww-data# encoding: utf-8 require 'rubygems/requirement' require 'set' module Cliver # This is how a dependency is specified. class Dependency # An exception class raised when assertion is not met NotMet = Class.new(ArgumentError) # An exception that is raised when executable present, but # no version that matches the requirements is present. VersionMismatch = Class.new(Dependency::NotMet) # An exception that is raised when executable is not present at all. NotFound = Class.new(Dependency::NotMet) # A pattern for extracting a {Gem::Version}-parsable version PARSABLE_GEM_VERSION = /[0-9]+(.[0-9]+){0,4}(.[a-zA-Z0-9]+)?/.freeze # @overload initialize(executables, *requirements, options = {}) # @param executables [String,Array] api-compatible executable names # e.g, ['python2','python'] # @param requirements [Array, String] splat of strings # whose elements follow the pattern # [] # Where is optional (default '='') and in the set # '=', '!=', '>', '<', '>=', '<=', or '~>' # And is dot-separated integers with optional # alphanumeric pre-release suffix. See also # {http://docs.rubygems.org/read/chapter/16 Specifying Versions} # @param options [Hash] # @option options [Cliver::Detector] :detector (Detector.new) # @option options [#to_proc, Object] :detector (see Detector::generate) # @option options [#to_proc] :filter ({Cliver::Filter::IDENTITY}) # @option options [Boolean] :strict (false) # true - fail if first match on path fails # to meet version requirements. # This is used for Cliver::assert. # false - continue looking on path until a # sufficient version is found. # @option options [String] :path ('*') the path on which to search # for executables. If an asterisk (`*`) is # included in the supplied string, it is # replaced with `ENV['PATH']` # # @yieldparam executable_path [String] (see Detector#detect_version) # @yieldreturn [String] containing a version that, once filtered, can be # used for comparrison. def initialize(executables, *args, &detector) options = args.last.kind_of?(Hash) ? args.pop : {} @detector = Detector::generate(detector || options[:detector]) @filter = options.fetch(:filter, Filter::IDENTITY).extend(Filter) @path = options.fetch(:path, '*') @strict = options.fetch(:strict, false) @executables = Array(executables).dup.freeze @requirement = args unless args.empty? check_compatibility! end # One of these things is not like the other ones... # Some feature combinations just aren't compatible. This method ensures # the the features selected for this object are compatible with each-other. # @return [void] # @raise [ArgumentError] if incompatibility found def check_compatibility! case when @executables.any? {|exe| exe[File::SEPARATOR] && !File.absolute_path?(exe) } # if the executable contains a path component, it *must* be absolute. raise ArgumentError, "Relative-path executable requirements are not supported." end end # Get all the installed versions of the api-compatible executables. # If a block is given, it yields once per found executable, lazily. # @yieldparam executable_path [String] # @yieldparam version [String] # @yieldreturn [Boolean] - true if search should stop. # @return [Hash] executable_path, version def installed_versions return enum_for(:installed_versions) unless block_given? find_executables.each do |executable_path| version = detect_version(executable_path) break(2) if yield(executable_path, version) end end # The non-raise variant of {#detect!} # @return (see #detect!) # or nil if no match found. def detect detect! rescue Dependency::NotMet nil end # Detects an installed version of the executable that matches the # requirements. # @return [String] path to an executable that meets the requirements # @raise [Cliver::Dependency::NotMet] if no match found def detect! installed = {} installed_versions.each do |path, version| installed[path] = version return path if ENV['CLIVER_NO_VERIFY'] return path if requirement_satisfied_by?(version) strict? end # dependency not met. raise the appropriate error. raise_not_found! if installed.empty? raise_version_mismatch!(installed) end private # @api private # @return [Gem::Requirement] def filtered_requirement @filtered_requirement ||= begin Gem::Requirement.new(@filter.requirements(@requirement)) end end # @api private # @param raw_version [String] # @return [Boolean] def requirement_satisfied_by?(raw_version) return true unless @requirement parsable_version = @filter.apply(raw_version)[PARSABLE_GEM_VERSION] parsable_version || raise(ArgumentError) # TODO: make descriptive filtered_requirement.satisfied_by? Gem::Version.new(parsable_version) end # @api private # @raise [Cliver::Dependency::NotFound] with appropriate error message def raise_not_found! raise Dependency::NotFound.new( "Could not find an executable #{@executables} on your path.") end # @api private # @raise [Cliver::Dependency::VersionMismatch] with appropriate error message # @param installed [Hash] the found versions def raise_version_mismatch!(installed) raise Dependency::VersionMismatch.new( "Could not find an executable #{executable_description} that " + "matched the requirements #{requirements_description}. " + "Found versions were #{installed.inspect}.") end # @api private # @return [String] a plain-language representation of the executables # for which we were searching def executable_description quoted_exes = @executables.map {|exe| "'#{exe}'" } return quoted_exes.first if quoted_exes.size == 1 last_quoted_exec = quoted_exes.pop "#{quoted_exes.join(', ')} or #{last_quoted_exec}" end # @api private # @return [String] a plain-language representation of the requirements def requirements_description @requirement.map {|req| "'#{req}'" }.join(', ') end # If strict? is true, only attempt the first matching executable on the path # @api private # @return [Boolean] def strict? false | @strict end # Given a path to an executable, detect its version # @api private # @param executable_path [String] # @return [String] # @raise [ArgumentError] if version cannot be detected. def detect_version(executable_path) # No need to shell out if we are only checking its presence. return '99.version_detection_not_required' unless @requirement raw_version = @detector.to_proc.call(executable_path) raw_version || raise(ArgumentError, "The detector #{@detector} failed to detect the" + "version of the executable at '#{executable_path}'") end # Analog of Windows `where` command, or a `which` that finds *all* # matching executables on the supplied path. # @return [Enumerable] - the executables found, lazily. def find_executables return enum_for(:find_executables) unless block_given? exts = (ENV.has_key?('PATHEXT') ? ENV.fetch('PATHEXT').split(';') : []) << '' paths = @path.sub('*', ENV['PATH']).split(File::PATH_SEPARATOR) raise ArgumentError.new('No PATH to search!') if paths.empty? cmds = strict? ? @executables.first(1) : @executables lookup_cache = Set.new cmds.product(paths, exts).map do |cmd, path, ext| exe = File.absolute_path?(cmd) ? cmd : File.expand_path("#{cmd}#{ext}", path) next unless lookup_cache.add?(exe) # don't yield the same exe path 2x next unless File.executable?(exe) yield exe end end end end cliver-0.3.2/lib/cliver/shell_capture.rb0000644000004100000410000000222412576035272020252 0ustar www-datawww-datarequire 'open3' module Cliver class ShellCapture attr_reader :stdout, :stderr, :command_found # @overlaod initialize(command) # @param command [String] the command to run # @overload initialize(command) # @param command [Array] the command to run; elements in # the supplied array will be shelljoined. # @return [void] def initialize(command) command = command.shelljoin if command.kind_of?(Array) @stdout = @stderr = '' begin Open3.popen3(command) do |i, o, e| @stdout = o.read.chomp @stderr = e.read.chomp end # Fix for ruby 1.8.7 (and probably earlier): # Open3.popen3 does not raise anything there, but the error goes to STDERR. if @stderr =~ /open3.rb:\d+:in `exec': No such file or directory -.*\(Errno::ENOENT\)/ or @stderr =~ /An exception occurred in a forked block\W+No such file or directory.*\(Errno::ENOENT\)/ @stderr = '' @command_found = false else @command_found = true end rescue Errno::ENOENT, IOError @command_found = false end end end end cliver-0.3.2/lib/cliver/filter.rb0000644000004100000410000000125412576035272016707 0ustar www-datawww-data# encoding: utf-8 module Cliver # A Namespace to hold filter procs module Filter # The identity filter returns its input unchanged. IDENTITY = proc { |version| version } # Apply to a list of requirements # @param requirements [Array] # @return [Array] def requirements(requirements) requirements.map do |requirement| req_parts = requirement.split(/\b(?=\d)/, 2) version = req_parts.last version.replace apply(version) req_parts.join end end # Apply to some input # @param version [String] # @return [String] def apply(version) to_proc.call(version) end end end cliver-0.3.2/lib/cliver/version.rb0000644000004100000410000000014712576035272017107 0ustar www-datawww-data# encoding: utf-8 module Cliver # Cliver follows {http://semver.org SemVer} VERSION = '0.3.2' end cliver-0.3.2/lib/cliver/detector.rb0000644000004100000410000000616612576035272017242 0ustar www-datawww-data# encoding: utf-8 require 'shellwords' module Cliver # Default implementation of the detector needed by Cliver::Assertion, # which will take anything that #respond_to?(:to_proc) class Detector < Struct.new(:command_arg, :version_pattern) # @param detector_argument [#call, Object] # If detector_argument responds to #call, return it; otherwise attempt # to create an instance of self. def self.generate(detector_argument) return detector_argument if detector_argument.respond_to?(:call) new(*Array(detector_argument)) end # Default pattern to use when searching {#version_command} output DEFAULT_VERSION_PATTERN = /(version ?)?[0-9][.0-9a-z]+/i.freeze # Default command argument to use against the executable to get # version output DEFAULT_COMMAND_ARG = '--version'.freeze # Forgiving input, allows either argument if only one supplied. # # @overload initialize(*command_args) # @param command_args [Array] # @overload initialize(version_pattern) # @param version_pattern [Regexp] # @overload initialize(*command_args, version_pattern) # @param command_args [Array] # @param version_pattern [Regexp] def initialize(*args) version_pattern = args.pop if args.last.kind_of?(Regexp) command_args = args unless args.empty? super(command_args, version_pattern) end # @param executable_path [String] - the path to the executable to test # @return [String] - should be contain {Gem::Version}-parsable # version number. def detect_version(executable_path) capture = ShellCapture.new(version_command(executable_path)) unless capture.command_found raise Cliver::Dependency::NotFound.new( "Could not find an executable at given path '#{executable_path}'." + "If this path was not specified explicitly, it is probably a " + "bug in [Cliver](https://github.com/yaauie/cliver/issues)." ) end capture.stdout[version_pattern] || capture.stderr[version_pattern] end # This is the interface that any detector must have. # If not overridden, returns a proc that wraps #detect_version # @see #detect_version # @return [Proc] following method signature of {#detect_version} def to_proc method(:detect_version).to_proc end # The pattern to match the version in {#version_command}'s output. # Defaults to {DEFAULT_VERSION_PATTERN} # @return [Regexp] - the pattern used against the output # of the #version_command, which should # contain a {Gem::Version}-parsable substring. def version_pattern super || DEFAULT_VERSION_PATTERN end # The argument to pass to the executable to get current version # Defaults to {DEFAULT_COMMAND_ARG} # @return [String, Array] def command_arg super || DEFAULT_COMMAND_ARG end # @param executable_path [String] the executable to test # @return [Array] def version_command(executable_path) [executable_path, *Array(command_arg)] end end end cliver-0.3.2/lib/cliver.rb0000644000004100000410000000627312576035272015430 0ustar www-datawww-data# encoding: utf-8 require File.expand_path('../core_ext/file', __FILE__) require 'cliver/version' require 'cliver/dependency' require 'cliver/shell_capture' require 'cliver/detector' require 'cliver/filter' # Cliver is tool for making dependency assertions against # command-line executables. module Cliver # The primary interface for the Cliver gem allows detection of an executable # on your path that matches a version requirement, or raise an appropriate # exception to make resolution simple and straight-forward. # @see Cliver::Dependency # @overload (see Cliver::Dependency#initialize) # @param (see Cliver::Dependency#initialize) # @raise (see Cliver::Dependency#detect!) # @return (see Cliver::Dependency#detect!) def self.detect!(*args, &block) Dependency::new(*args, &block).detect! end # A non-raising variant of {::detect!}, simply returns false if dependency # cannot be found. # @see Cliver::Dependency # @overload (see Cliver::Dependency#initialize) # @param (see Cliver::Dependency#initialize) # @raise (see Cliver::Dependency#detect) # @return (see Cliver::Dependency#detect) def self.detect(*args, &block) Dependency::new(*args, &block).detect end # A legacy interface for {::detect} with the option `strict: true`, ensures # that the first executable on your path matches the requirements. # @see Cliver::Dependency # @overload (see Cliver::Dependency#initialize) # @param (see Cliver::Dependency#initialize) # @option options [Boolean] :strict (true) @see Cliver::Dependency::initialize # @raise (see Cliver::Dependency#detect!) # @return (see Cliver::Dependency#detect!) def self.assert(*args, &block) options = args.last.kind_of?(Hash) ? args.pop : {} args << options.merge(:strict => true) Dependency::new(*args, &block).detect! end # Verify an absolute-path to an executable. # @overload verify!(executable, *requirements, options = {}) # @param executable [String] absolute path to an executable # @param requirements (see Cliver::Dependency#initialize) # @option options (see Cliver::Dependency::initialize) # @raise (see Cliver::Dependency#detect!) # @return (see Cliver::Dependency#detect!) def self.verify!(executable, *args, &block) unless File.absolute_path?(executable) raise ArgumentError, "executable path must be absolute, " + "got '#{executable.inspect}'." end options = args.last.kind_of?(Hash) ? args.pop : {} args << options.merge(:path => '.') # ensure path non-empty. Dependency::new(executable, *args, &block).detect! end extend self # Wraps Cliver::assert and returns truthy/false instead of raising # @see Cliver::assert # @overload (see Cliver::Assertion#initialize) # @param (see Cliver::Assertion#initialize) # @return [False,String] either returns false or the reason why the # assertion was unmet. def dependency_unmet?(*args, &block) Cliver.assert(*args, &block) false rescue Dependency::NotMet => error # Cliver::Assertion::VersionMismatch -> 'Version Mismatch' reason = error.class.name.split(':').last.gsub(/([a-z])([A-Z])/, '\\1 \\2') "#{reason}: #{error.message}" end end cliver-0.3.2/metadata.yml0000644000004100000410000000771512576035272015356 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: cliver version: !ruby/object:Gem::Version version: 0.3.2 prerelease: platform: ruby authors: - Ryan Biesemeyer autorequire: bindir: bin cert_chain: [] date: 2013-12-13 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: bundler requirement: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: '1.3' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: '1.3' - !ruby/object:Gem::Dependency name: rake requirement: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: rspec requirement: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: ruby-appraiser-reek requirement: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: ruby-appraiser-rubocop requirement: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: yard requirement: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' description: Assertions for command-line dependencies email: - ryan@yaauie.com executables: [] extensions: [] extra_rdoc_files: [] files: - .githooks/pre-commit/ruby-appraiser - .gitignore - .travis.yml - CONTRIBUTING.md - Gemfile - LICENSE.txt - README.md - Rakefile - cliver.gemspec - lib/cliver.rb - lib/cliver/dependency.rb - lib/cliver/detector.rb - lib/cliver/filter.rb - lib/cliver/shell_capture.rb - lib/cliver/version.rb - lib/core_ext/file.rb - spec/cliver/detector_spec.rb - spec/cliver/shell_capture_spec.rb - spec/cliver_spec.rb - spec/core_ext/file_spec.rb - spec/spec_helper.rb - spec/support/executable_mock.rb homepage: https://www.github.com/yaauie/cliver licenses: - MIT post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 1.8.24 signing_key: specification_version: 3 summary: Cross-platform version constraints for cli tools test_files: - spec/cliver/detector_spec.rb - spec/cliver/shell_capture_spec.rb - spec/cliver_spec.rb - spec/core_ext/file_spec.rb - spec/spec_helper.rb - spec/support/executable_mock.rb has_rdoc: yard cliver-0.3.2/.gitignore0000644000004100000410000000023212576035272015026 0ustar www-datawww-data*.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp cliver-0.3.2/CONTRIBUTING.md0000644000004100000410000000247612576035272015303 0ustar www-datawww-data# Contributing `Cliver` is [MIT-liennsed](LICENSE.txt) and intends to follow the [pull-request-hack][]. ## Git-Flow `Cliver` follows the [git-flow][] branching model, which means that every commit on `master` is a release. The default working branch is `develop`, so in general please keep feature pull-requests based against the current `develop`. - fork cliver - use the git-flow model to start your feature or hotfix - make some commits (please include specs) - submit a pull-request ## Bug Reporting Please include clear steps-to-reproduce. Spec files are especially welcome; a failing spec can be contributed as a pull-request against `develop`. ## Ruby Appraiser `Cliver` uses the [ruby-appraiser][] gem via [pre-commit][] hook, which can be activated by installing [icefox/git-hooks][] and running `git-hooks --install`. Reek and Rubocop are strong guidelines; use them to reduce defects as much as you can, but if you believe clarity will be sacrificed they can be bypassed with the `--no-verify` flag. [git-flow]: http://nvie.com/posts/a-successful-git-branching-model/ [pre-commit]: .githooks/pre-commit/ruby-appraiser [ruby-appraiser]: https://github.com/simplymeasured/ruby-appraiser [icefox/git-hooks]: https://github.com/icefox/git-hooks [pull-request-hack]: http://felixge.de/2013/03/11/the-pull-request-hack.html cliver-0.3.2/cliver.gemspec0000644000004100000410000000224112576035272015671 0ustar www-datawww-data# encoding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'cliver/version' Gem::Specification.new do |spec| RUBY_18 = RUBY_VERSION[/\A1\.8\..*/] spec.name = 'cliver' spec.version = Cliver::VERSION spec.authors = ['Ryan Biesemeyer'] spec.email = ['ryan@yaauie.com'] spec.description = 'Assertions for command-line dependencies' spec.summary = 'Cross-platform version constraints for cli tools' spec.homepage = 'https://www.github.com/yaauie/cliver' spec.license = 'MIT' spec.files = `git ls-files`.split($RS) spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) } spec.test_files = spec.files.grep(/^(test|spec|features)\//) spec.require_paths = ['lib'] spec.has_rdoc = 'yard' spec.add_development_dependency 'bundler', '~> 1.3' spec.add_development_dependency 'rake' spec.add_development_dependency 'rspec' spec.add_development_dependency 'ruby-appraiser-reek' unless RUBY_18 spec.add_development_dependency 'ruby-appraiser-rubocop' unless RUBY_18 spec.add_development_dependency 'yard' end cliver-0.3.2/README.md0000644000004100000410000001072012576035272014320 0ustar www-datawww-data# Cliver Sometimes Ruby apps shell out to command-line executables, but there is no standard way to ensure those underlying dependencies are met. Users usually find out via a nasty stack-trace and whatever wasn't captured on stderr, or by the odd behavior exposed by a version mismatch. `Cliver` is a simple gem that provides an easy way to detect and use command-line dependencies. Under the covers, it uses [rubygems/requirements][] so it supports the version requirements you're used to providing in your gemspec. ## Usage ### Detect and Detect! The detect methods search your entire path until they find a matching executable or run out of places to look. ```ruby # no version requirements Cliver.detect('subl') # => '/Users/yaauie/.bin/subl' # one version requirement Cliver.detect('bzip2', '~> 1.0.6') # => '/usr/bin/bzip2' # many version requirements Cliver.detect('racc', '>= 1.0', '< 1.4.9') # => '/Users/yaauie/.rbenv/versions/1.9.3-p194/bin/racc' # dependency not met Cliver.detect('racc', '~> 10.4.9') # => nil # detect! raises Cliver::Dependency::NotMet exceptions when the dependency # cannot be met. Cliver.detect!('ruby', '1.8.5') # Cliver::Dependency::VersionMismatch # Could not find an executable ruby that matched the # requirements '1.8.5'. Found versions were {'/usr/bin/ruby'=> '1.8.7'} Cliver.detect!('asdfasdf') # Cliver::Dependency::NotFound # Could not find an executable asdfasdf on your path ``` ### Assert The assert method is useful when you do not have control over how the dependency is shelled-out to and require that the first matching executable on your path satisfies your version requirements. It is the equivalent of the detect! method with `strict: true` option. ## Advanced Usage: ### Version Detectors Some programs don't provide nice 'version 1.2.3' strings in their `--version` output; `Cliver` lets you provide your own version detector with a pattern. ```ruby Cliver.assert('python', '~> 1.7', detector: /(?<=Python )[0-9][.0-9a-z]+/) ``` Other programs don't provide a standard `--version`; `Cliver::Detector` also allows you to provide your own arg to get the version: ```ruby # single-argument command Cliver.assert('janky', '~> 10.1.alpha', detector: '--release-version') # multi-argument command Cliver.detect('ruby', '~> 1.8.7', detector: [['-e', 'puts RUBY_VERSION']]) ``` You can use both custom pattern and custom command by supplying an array: ```ruby Cliver.assert('janky', '~> 10.1.alpha', detector: ['--release-version', /.*/]) ``` And even supply multiple arguments in an Array, too: ```ruby # multi-argument command Cliver.detect('ruby', '~> 1.8.7', detector: ['-e', 'puts RUBY_VERSION']) ``` Alternatively, you can supply your own detector (anything that responds to `#to_proc`) in the options hash or as a block, so long as it returns a `Gem::Version`-parsable version number; if it returns nil or false when version requirements are given, a descriptive `ArgumentError` is raised. ```ruby Cliver.assert('oddball', '~> 10.1.alpha') do |oddball_path| File.read(File.expand_path('../VERSION', oddball_path)).chomp end ``` And since some programs don't always spit out nice semver-friendly version numbers at all, a filter proc can be supplied to clean it up. Note how the filter is applied to both your requirements and the executable's output: ### Filters ```ruby Cliver.assert('built-thing', '~> 2013.4r8273', filter: proc { |ver| ver.tr('r','.') }) ``` Since `Cliver` uses `Gem::Requirement` for version comparrisons, it obeys all the same rules including pre-release semantics. ### Search Path By default, Cliver uses `ENV['PATH']` as its search path, but you can provide your own. If the asterisk symbol (`*`) is included in your string, it is replaced `ENV['PATH']`. ```ruby Cliver.detect('gadget', path: './bins/:*') # => 'Users/yaauie/src/project-a/bins/gadget' ``` ## Supported Platforms The goal is to have full support for all platforms running ruby >= 1.9.2, including rubinius and jruby implementations, as well as basic support for legacy ruby 1.8.7. Windows has support in the codebase, but is not available as a build target in [travis_ci][]. ## See Also: - [YARD Documentation][yard-docs] - [Contributing](CONTRIBUTING.md) - [License](LICENSE.txt) [rubygems/requirements]: https://github.com/rubygems/rubygems/blob/master/lib/rubygems/requirement.rb [yard-docs]: http://yaauie.github.io/cliver/ [travis-ci]: https://travis-ci.org/yaauie/cliver cliver-0.3.2/.githooks/0000755000004100000410000000000012576035272014746 5ustar www-datawww-datacliver-0.3.2/.githooks/pre-commit/0000755000004100000410000000000012576035272017022 5ustar www-datawww-datacliver-0.3.2/.githooks/pre-commit/ruby-appraiser0000755000004100000410000000104512576035272021715 0ustar www-datawww-data#!/bin/bash echo -e "\033[0;36mRuby Appraiser: running\033[0m" bundle exec ruby-appraiser --mode=staged reek rubocop result_code=$? if [ $result_code -gt "0" ]; then echo -en "\033[0;31m" # RED echo "[✘] Ruby Appraiser found newly-created defects and " echo " has blocked your commit." echo " Fix the defects and commit again." echo " To bypass, commit again with --no-verify." echo -en "\033[0m" # RESET exit $result_code else echo -en "\033[0;32m" # GREEN echo "[✔] Ruby Appraiser ok" echo -en "\033[0m" #RESET fi