librarian-0.1.1/0000755000004100000410000000000012210667660013512 5ustar www-datawww-datalibrarian-0.1.1/.travis.yml0000644000004100000410000000031712210667660015624 0ustar www-datawww-datalanguage: ruby script: rspec spec -b rvm: - 1.8.7 - 1.9.2 - 1.9.3 - 2.0.0 - rbx-18mode - rbx-19mode - jruby-18mode - jruby-19mode - ruby-head matrix: allow_failures: - rvm: ruby-head librarian-0.1.1/README.md0000644000004100000410000000310212210667660014765 0ustar www-datawww-dataLibrarian [![Build Status](https://secure.travis-ci.org/applicationsonline/librarian.png)](http://travis-ci.org/applicationsonline/librarian) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/applicationsonline/librarian) ========= Librarian is a framework for writing bundlers, which are tools that resolve, fetch, install, and isolate a project's dependencies, in Ruby. A bundler written with Librarian will expect you to provide a specfile listing your project's declared dependencies, including any version constraints and including the upstream sources for finding them. Librarian can resolve the spec, write a lockfile listing the full resolution, fetch the resolved dependencies, install them, and isolate them in your project. A bundler written with Librarian will be similar in kind to [Bundler](http://gembundler.com), the bundler for Ruby gems that many modern Rails applications use. How to Contribute ----------------- ### Running the tests Ensure the gem dependencies are installed: $ bundle Run the tests with the default rake task: $ [bundle exec] rake or directly with the rspec command: $ [bundle exec] rspec spec ### Installing locally Ensure the gem dependencies are installed: $ bundle Install from the repository: $ [bundle exec] rake install ### Reporting Issues Please include a reproducible test case. License ------- Written by Jay Feldblum. Copyright (c) 2011-2013 ApplicationsOnline, LLC. Released under the terms of the MIT License. For further information, please see the file `LICENSE.txt`. librarian-0.1.1/Rakefile0000644000004100000410000000122312210667660015155 0ustar www-datawww-datarequire 'bundler' require 'rspec/core/rake_task' module Bundler class GemHelper def build_gem_with_built_spec spec = Gem::Specification.load(spec_path) spec_ruby = spec.to_ruby original_spec_path = spec_path + ".original" FileUtils.mv(spec_path, original_spec_path) File.open(spec_path, "wb"){|f| f.write(spec_ruby)} build_gem_without_built_spec ensure FileUtils.mv(original_spec_path, spec_path) end alias build_gem_without_built_spec build_gem alias build_gem build_gem_with_built_spec end end Bundler::GemHelper.install_tasks RSpec::Core::RakeTask.new('spec') task :default => :spec librarian-0.1.1/spec/0000755000004100000410000000000012210667660014444 5ustar www-datawww-datalibrarian-0.1.1/spec/functional/0000755000004100000410000000000012210667660016606 5ustar www-datawww-datalibrarian-0.1.1/spec/functional/cli_spec.rb0000644000004100000410000000102712210667660020714 0ustar www-datawww-datarequire "securerandom" require "librarian/rspec/support/cli_macro" require "librarian/mock/cli" module Librarian module Mock describe Cli do include Librarian::RSpec::Support::CliMacro describe "version" do before do cli! "version" end it "should print the version" do stdout.should == strip_heredoc(<<-STDOUT) librarian-#{Librarian::VERSION} librarian-mock-#{Librarian::Mock::VERSION} STDOUT end end end end end librarian-0.1.1/spec/functional/source/0000755000004100000410000000000012210667660020106 5ustar www-datawww-datalibrarian-0.1.1/spec/functional/source/git_spec.rb0000644000004100000410000000170312210667660022231 0ustar www-datawww-datarequire "pathname" require "securerandom" require "librarian/error" require "librarian/source/git" describe Librarian::Source::Git do let(:project_path) do project_path = Pathname.new(__FILE__).expand_path project_path = project_path.dirname until project_path.join("Rakefile").exist? project_path end let(:tmp_path) { project_path + "tmp/spec/functional/source/git" } after { tmp_path.rmtree if tmp_path && tmp_path.exist? } let(:cache_path) { tmp_path + "cache" } context "when the remote is bad" do let(:remote) { tmp_path.join(SecureRandom.hex(8)).to_s } let(:logger) { double(:debug => nil, :info => nil) } let(:env) { double(:ui => nil, :logger => logger, :cache_path => cache_path) } let(:source) { described_class.new(env, remote, {}) } it "fails when caching" do expect { source.cache! }.to raise_error Librarian::Error, /^fatal: repository .+ does not exist$/ # from git end end end librarian-0.1.1/spec/functional/source/git/0000755000004100000410000000000012210667660020671 5ustar www-datawww-datalibrarian-0.1.1/spec/functional/source/git/repository_spec.rb0000644000004100000410000001101712210667660024447 0ustar www-datawww-datarequire "fileutils" require "pathname" require "securerandom" require "librarian/posix" require "librarian/source/git/repository" describe Librarian::Source::Git::Repository do let(:env) do double(:ui => nil, :logger => double(:debug => nil, :info => nil)) end let(:project_path) do project_path = Pathname.new(__FILE__).expand_path project_path = project_path.dirname until project_path.join("Rakefile").exist? project_path end let(:tmp_path) { project_path + "tmp/spec/functional/source/git/repository" } after { tmp_path.rmtree if tmp_path && tmp_path.exist? } let(:git_source_path) { tmp_path + SecureRandom.hex(16) } let(:branch) { "the-branch" } let(:tag) { "the-tag" } let(:atag) { "the-atag" } def cmd!(command) Librarian::Posix.run! command end def git!(command) cmd!([described_class.bin] + command) end before do git_source_path.mkpath Dir.chdir(git_source_path) do git! %W[init] git! %W[config user.name Simba] git! %W[config user.email simba@savannah-pride.gov] # master FileUtils.touch "butter.txt" git! %W[add butter.txt] git! %W[commit -m #{"Initial Commit"}] # branch git! %W[checkout -b #{branch} --quiet] FileUtils.touch "jam.txt" git! %W[add jam.txt] git! %W[commit -m #{"Branch Commit"}] git! %W[checkout master --quiet] # tag/atag git! %W[checkout -b deletable --quiet] FileUtils.touch "jelly.txt" git! %W[add jelly.txt] git! %W[commit -m #{"Tag Commit"}] git! %W[tag #{tag}] git! %W[tag -am #{"Annotated Tag Commit"} #{atag}] git! %W[checkout master --quiet] git! %W[branch -D deletable] end end describe ".bin" do specify { described_class.bin.should_not be_empty } end describe ".git_version" do specify { described_class.git_version.should =~ /^\d+(\.\d+)+$/ } end context "the original" do subject { described_class.new(env, git_source_path) } it "should recognize it" do subject.should be_git end it "should not list any remotes for it" do subject.remote_names.should be_empty end it "should not list any remote branches for it" do subject.remote_branch_names.should be_empty end it "should have divergent shas for master, branch, tag, and atag" do revs = %W[ master #{branch} #{tag} #{atag} ] rev_parse = proc{|rev| git!(%W[rev-parse #{rev} --quiet]).strip} shas = Dir.chdir(git_source_path){revs.map(&rev_parse)} shas.map(&:class).uniq.should be == [String] shas.map(&:size).uniq.should be == [40] shas.uniq.should be == shas end end context "a clone" do let(:git_clone_path) { tmp_path + SecureRandom.hex(16) } subject { described_class.clone!(env, git_clone_path, git_source_path) } let(:master_sha) { subject.hash_from("origin", "master") } let(:branch_sha) { subject.hash_from("origin", branch) } let(:tag_sha) { subject.hash_from("origin", tag) } let(:atag_sha) { subject.hash_from("origin", atag) } it "should recognize it" do subject.should be_git end it "should have a single remote for it" do subject.should have(1).remote_names end it "should have a remote with the expected name" do subject.remote_names.first.should == "origin" end it "should have the remote branch" do subject.remote_branch_names["origin"].should include branch end it "should be checked out on the master" do subject.should be_checked_out(master_sha) end context "checking out the branch" do before do subject.checkout! branch end it "should be checked out on the branch" do subject.should be_checked_out(branch_sha) end it "should not be checked out on the master" do subject.should_not be_checked_out(master_sha) end end context "checking out the tag" do before do subject.checkout! tag end it "should be checked out on the tag" do subject.should be_checked_out(tag_sha) end it "should not be checked out on the master" do subject.should_not be_checked_out(master_sha) end end context "checking out the annotated tag" do before do subject.checkout! atag end it "should be checked out on the annotated tag" do subject.should be_checked_out(atag_sha) end it "should not be checked out on the master" do subject.should_not be_checked_out(master_sha) end end end end librarian-0.1.1/spec/functional/posix_spec.rb0000644000004100000410000000161512210667660021312 0ustar www-datawww-datarequire "librarian/posix" describe Librarian::Posix do let(:project_path) do project_path = Pathname.new(__FILE__).expand_path project_path = project_path.dirname until project_path.join("Rakefile").exist? project_path end let(:tmp_path) { project_path + "tmp/spec/functional/posix" } after { tmp_path.rmtree if tmp_path && tmp_path.exist? } describe ".run!" do it "returns the stdout" do res = described_class.run!(%w[echo hello there]).strip res.should be == "hello there" end it "changes directory" do tmp_path.mkpath res = described_class.run!(%w[pwd], :chdir => tmp_path).strip res.should be == tmp_path.to_s end it "reads the env" do res = described_class.run!(%w[env], :env => {"KOALA" => "BEAR"}) line = res.lines.find{|l| l.start_with?("KOALA=")}.strip line.should be == "KOALA=BEAR" end end end librarian-0.1.1/spec/unit/0000755000004100000410000000000012210667660015423 5ustar www-datawww-datalibrarian-0.1.1/spec/unit/spec_change_set_spec.rb0000644000004100000410000001025012210667660022072 0ustar www-datawww-datarequire 'librarian' require 'librarian/spec_change_set' require 'librarian/mock' module Librarian describe SpecChangeSet do let(:env) { Mock::Environment.new } let(:resolver) { env.resolver } context "a simple root removal" do it "should work" do env.registry :clear => true do source 'source-1' do spec 'butter', '1.0' spec 'jam', '1.0' end end spec = env.dsl do src 'source-1' dep 'butter' dep 'jam' end lock = resolver.resolve(spec) lock.should be_correct spec = env.dsl do src 'source-1' dep 'jam' end changes = described_class.new(env, spec, lock) changes.should_not be_same manifests = ManifestSet.new(changes.analyze).to_hash manifests.should have_key('jam') manifests.should_not have_key('butter') end end context "a simple root add" do it "should work" do env.registry :clear => true do source 'source-1' do spec 'butter', '1.0' spec 'jam', '1.0' end end spec = env.dsl do src 'source-1' dep 'jam' end lock = resolver.resolve(spec) lock.should be_correct spec = env.dsl do src 'source-1' dep 'butter' dep 'jam' end changes = described_class.new(env, spec, lock) changes.should_not be_same manifests = ManifestSet.new(changes.analyze).to_hash manifests.should have_key('jam') manifests.should_not have_key('butter') end end context "a simple root change" do context "when the change is consistent" do it "should work" do env.registry :clear => true do source 'source-1' do spec 'butter', '1.0' spec 'jam', '1.0' spec 'jam', '1.1' end end spec = env.dsl do src 'source-1' dep 'butter' dep 'jam', '= 1.1' end lock = resolver.resolve(spec) lock.should be_correct spec = env.dsl do src 'source-1' dep 'butter' dep 'jam', '>= 1.0' end changes = described_class.new(env, spec, lock) changes.should_not be_same manifests = ManifestSet.new(changes.analyze).to_hash manifests.should have_key('butter') manifests.should have_key('jam') end end context "when the change is inconsistent" do it "should work" do env.registry :clear => true do source 'source-1' do spec 'butter', '1.0' spec 'jam', '1.0' spec 'jam', '1.1' end end spec = env.dsl do src 'source-1' dep 'butter' dep 'jam', '= 1.0' end lock = resolver.resolve(spec) lock.should be_correct spec = env.dsl do src 'source-1' dep 'butter' dep 'jam', '>= 1.1' end changes = described_class.new(env, spec, lock) changes.should_not be_same manifests = ManifestSet.new(changes.analyze).to_hash manifests.should have_key('butter') manifests.should_not have_key('jam') end end end context "a simple root source change" do it "should work" do env.registry :clear => true do source 'source-1' do spec 'butter', '1.0' end source 'source-2' do spec 'butter', '1.0' end end spec = env.dsl do src 'source-1' dep 'butter' end lock = resolver.resolve(spec) lock.should be_correct spec = env.dsl do src 'source-1' dep 'butter', :src => 'source-2' end changes = described_class.new(env, spec, lock) changes.should_not be_same manifests = ManifestSet.new(changes.analyze).to_hash manifests.should_not have_key('butter') end end end end librarian-0.1.1/spec/unit/mock/0000755000004100000410000000000012210667660016354 5ustar www-datawww-datalibrarian-0.1.1/spec/unit/mock/source/0000755000004100000410000000000012210667660017654 5ustar www-datawww-datalibrarian-0.1.1/spec/unit/mock/source/mock_spec.rb0000644000004100000410000000056512210667660022152 0ustar www-datawww-datarequire "librarian/mock" module Librarian module Mock module Source describe Mock do let(:env) { Librarian::Mock::Environment.new } describe ".new" do let(:source) { described_class.new(env, "source-a", {}) } subject { source } its(:environment) { should_not be_nil } end end end end end librarian-0.1.1/spec/unit/mock/environment_spec.rb0000644000004100000410000000105512210667660022260 0ustar www-datawww-datarequire "librarian/mock/environment" module Librarian::Mock describe Environment do let(:env) { described_class.new } describe "#version" do specify { env.version.should be == Librarian::VERSION } end describe "#adapter_module" do specify { env.adapter_module.should be Librarian::Mock } end describe "#adapter_name" do specify { env.adapter_name.should be == "mock" } end describe "#adapter_version" do specify { env.adapter_version.should be == Librarian::Mock::VERSION } end end end librarian-0.1.1/spec/unit/resolver_spec.rb0000644000004100000410000001424412210667660020630 0ustar www-datawww-datarequire "pathname" require "tmpdir" require "support/fakefs" require 'librarian/resolver' require 'librarian/spec_change_set' require 'librarian/mock' module Librarian describe Resolver do include ::Support::FakeFS let(:env) { Mock::Environment.new } let(:resolver) { env.resolver } context "a simple specfile" do before do env.registry :clear => true do source 'source-1' do spec 'butter', '1.1' end end end let(:spec) do env.dsl do src 'source-1' dep 'butter' end end let(:resolution) { resolver.resolve(spec) } specify { resolution.should be_correct } end context "a specfile with a dep from one src depending on a dep from another src" do before do env.registry :clear => true do source 'source-1' do spec 'butter', '1.1' end source 'source-2' do spec 'jam', '1.2' do dependency 'butter', '>= 1.0' end end end end let(:spec) do env.dsl do src 'source-1' src 'source-2' do dep 'jam' end end end let(:resolution) { resolver.resolve(spec) } specify { resolution.should be_correct } end context "a specfile with a dep in multiple sources" do before do env.registry :clear => true do source 'source-1' do spec 'butter', '1.0' spec 'butter', '1.1' end source 'source-2' do spec 'butter', '1.0' spec 'butter', '1.1' end source 'source-3' do spec 'butter', '1.0' end end end let(:spec) do env.dsl do src 'source-1' src 'source-2' dep 'butter', '>= 1.1' end end it "should have the expected number of sources" do spec.should have(2).sources end let(:resolution) { resolver.resolve(spec) } specify { resolution.should be_correct } it "should have the manifest from the final source with a matching manifest" do manifest = resolution.manifests.find{|m| m.name == "butter"} manifest.source.name.should == "source-2" end end context "a specfile with a dep depending on a nonexistent dep" do before do env.registry :clear => true do source 'source-1' do spec 'jam', '1.2' do dependency 'butter', '>= 1.0' end end end end let(:spec) do env.dsl do src 'source-1' dep 'jam' end end let(:resolution) { resolver.resolve(spec) } specify { resolution.should be_nil } end context "a specfile with conflicting constraints" do before do env.registry :clear => true do source 'source-1' do spec 'butter', '1.0' spec 'butter', '1.1' spec 'jam', '1.2' do dependency 'butter', '1.1' end end end end let(:spec) do env.dsl do src 'source-1' dep 'butter', '1.0' dep 'jam' end end let(:resolution) { resolver.resolve(spec) } specify { resolution.should be_nil } end context "updating" do it "should not work" do env.registry :clear => true do source 'source-1' do spec 'butter', '1.0' spec 'butter', '1.1' spec 'jam', '1.2' do dependency 'butter' end end end first_spec = env.dsl do src 'source-1' dep 'butter', '1.1' dep 'jam' end first_resolution = resolver.resolve(first_spec) first_resolution.should be_correct first_manifests = first_resolution.manifests first_manifests_index = Hash[first_manifests.map{|m| [m.name, m]}] first_manifests_index['butter'].version.to_s.should == '1.1' second_spec = env.dsl do src 'source-1' dep 'butter', '1.0' dep 'jam' end locked_manifests = ManifestSet.deep_strip(first_manifests, ['butter']) second_resolution =resolver.resolve(second_spec, locked_manifests) second_resolution.should be_correct second_manifests = second_resolution.manifests second_manifests_index = Hash[second_manifests.map{|m| [m.name, m]}] second_manifests_index['butter'].version.to_s.should == '1.0' end end context "a change to the spec" do it "should work" do env.registry :clear => true do source 'source-1' do spec 'butter', '1.0' end source 'source-2' do spec 'butter', '1.0' end end spec = env.dsl do src 'source-1' dep 'butter' end lock = resolver.resolve(spec) lock.should be_correct spec = env.dsl do src 'source-1' dep 'butter', :src => 'source-2' end changes = SpecChangeSet.new(env, spec, lock) changes.should_not be_same manifests = ManifestSet.new(changes.analyze).to_hash manifests.should_not have_key('butter') lock = resolver.resolve(spec, changes.analyze) lock.should be_correct lock.manifests.map{|m| m.name}.should include('butter') manifest = lock.manifests.find{|m| m.name == 'butter'} manifest.should_not be_nil manifest.source.name.should == 'source-2' end end context "a pathname to a simple specfile" do let(:pwd) { Pathname(Dir.tmpdir) } let(:specfile_path) { pwd + "Mockfile" } before { FileUtils.mkpath(pwd) } def write!(path, text) Pathname(path).open("wb"){|f| f.write(text)} end it "loads the specfile with the __FILE__" do write! specfile_path, "src __FILE__" spec = env.dsl(specfile_path) spec.sources.should have(1).item source = spec.sources.first source.name.should == specfile_path.to_s end end end end librarian-0.1.1/spec/unit/source/0000755000004100000410000000000012210667660016723 5ustar www-datawww-datalibrarian-0.1.1/spec/unit/source/git_spec.rb0000644000004100000410000000132212210667660021043 0ustar www-datawww-datarequire "librarian" module Librarian module Source describe Git do let(:env) { Environment.new } describe "validating options for the specfile" do context "with only known options" do it "should not raise" do expect { described_class.from_spec_args(env, "some://git/repo.git", :ref => "megapatches") }. to_not raise_error end end context "with an unknown option" do it "should raise" do expect { described_class.from_spec_args(env, "some://git/repo.git", :branch => "megapatches") }. to raise_error Error, "unrecognized options: branch" end end end end end end librarian-0.1.1/spec/unit/lockfile_spec.rb0000644000004100000410000000277312210667660020563 0ustar www-datawww-datarequire 'librarian' require 'librarian/mock' module Librarian describe Lockfile do let(:env) { Mock::Environment.new } before do env.registry :clear => true do source 'source-1' do spec 'alpha', '1.1' end end end let(:spec) do env.dsl do src 'source-1' dep 'alpha', '1.1' end end let(:resolver) { env.resolver } let(:resolution) { resolver.resolve(spec) } context "sanity" do context "the resolution" do subject { resolution } it { should be_correct } it { should have(1).manifests } end end describe "#save" do let(:lockfile) { env.ephemeral_lockfile } let(:lockfile_text) { lockfile.save(resolution) } context "just saving" do it "should return the lockfile text" do lockfile_text.should_not be_nil end end context "saving and reloading" do let(:reloaded_resolution) { lockfile.load(lockfile_text) } it "should have the expected manifests" do reloaded_resolution.manifests.count.should == resolution.manifests.count end end context "bouncing" do let(:bounced_resolution) { lockfile.load(lockfile_text) } let(:bounced_lockfile_text) { lockfile.save(bounced_resolution) } it "should return the same lockfile text after bouncing as before bouncing" do bounced_lockfile_text.should == lockfile_text end end end end end librarian-0.1.1/spec/unit/environment_spec.rb0000644000004100000410000001354212210667660021333 0ustar www-datawww-datarequire "librarian/environment" require "support/with_env_macro" module Librarian describe Environment do include ::Support::WithEnvMacro let(:env) { described_class.new } describe "#adapter_module" do specify { env.adapter_module.should be nil } end describe "#adapter_name" do specify { env.adapter_name.should be nil } end describe "#adapter_version" do specify { env.adapter_version.should be nil } end describe "computing the home" do context "with the HOME env var" do with_env "HOME" => "/path/to/home" it "finds the home" do env.stub(:adapter_name).and_return("cat") env.config_db.underlying_home.to_s.should == "/path/to/home" end end context "without the HOME env var" do let!(:real_home) { File.expand_path("~") } with_env "HOME" => nil it "finds the home" do env.stub(:adapter_name).and_return("cat") env.config_db.underlying_home.to_s.should == real_home end end end describe "#http_proxy_uri" do context "sanity" do with_env "http_proxy" => nil it "should have a nil http proxy uri" do env.http_proxy_uri.should be_nil end end context "with a complex proxy" do with_env "http_proxy" => "admin:secret@example.com" it "should have the expcted http proxy uri" do env.http_proxy_uri.should == URI("http://admin:secret@example.com") end it "should have the expected host" do env.http_proxy_uri.host.should == "example.com" end it "should have the expected user" do env.http_proxy_uri.user.should == "admin" end it "should have the expected password" do env.http_proxy_uri.password.should == "secret" end end context "with a split proxy" do with_env "http_proxy" => "example.com", "http_proxy_user" => "admin", "http_proxy_pass" => "secret" it "should have the expcted http proxy uri" do env.http_proxy_uri.should == URI("http://admin:secret@example.com") end end end describe "#net_http_class" do let(:proxied_host) { "www.example.com" } context "sanity" do with_env "http_proxy" => nil it "should have the normal class" do env.net_http_class(proxied_host).should be Net::HTTP end it "should not be marked as a proxy class" do env.net_http_class(proxied_host).should_not be_proxy_class end end context "with a complex proxy" do with_env "http_proxy" => "admin:secret@example.com" it "should not by marked as a proxy class for localhost" do env.net_http_class('localhost').should_not be_proxy_class end it "should not have the normal class" do env.net_http_class(proxied_host).should_not be Net::HTTP end it "should have a subclass the normal class" do env.net_http_class(proxied_host).should < Net::HTTP end it "should be marked as a proxy class" do env.net_http_class(proxied_host).should be_proxy_class end it "should have the expected proxy attributes" do http = env.net_http_class(proxied_host).new("www.kernel.org") expected_attributes = { "host" => env.http_proxy_uri.host, "port" => env.http_proxy_uri.port, "user" => env.http_proxy_uri.user, "pass" => env.http_proxy_uri.password } actual_attributes = { "host" => http.proxy_address, "port" => http.proxy_port, "user" => http.proxy_user, "pass" => http.proxy_pass, } actual_attributes.should == expected_attributes end end context "with an excluded host" do with_env "http_proxy" => "admin:secret@example.com", "no_proxy" => "no.proxy.com, noproxy.com" context "with an exact match" do let(:proxied_host) { "noproxy.com" } it "should have the normal class" do env.net_http_class(proxied_host).should be Net::HTTP end it "should not be marked as a proxy class" do env.net_http_class(proxied_host).should_not be_proxy_class end end context "with a subdomain match" do let(:proxied_host) { "www.noproxy.com" } it "should have the normal class" do env.net_http_class(proxied_host).should be Net::HTTP end it "should not be marked as a proxy class" do env.net_http_class(proxied_host).should_not be_proxy_class end end context "with localhost" do let(:proxied_host) { "localhost" } it "should have the normal class" do env.net_http_class(proxied_host).should be Net::HTTP end it "should not be marked as a proxy class" do env.net_http_class(proxied_host).should_not be_proxy_class end end context "with 127.0.0.1" do let(:proxied_host) { "127.0.0.1" } it "should have the normal class" do env.net_http_class(proxied_host).should be Net::HTTP end it "should not be marked as a proxy class" do env.net_http_class(proxied_host).should_not be_proxy_class end end context "with a mismatch" do let(:proxied_host) { "www.example.com" } it "should have a subclass the normal class" do env.net_http_class(proxied_host).should < Net::HTTP end it "should be marked as a proxy class" do env.net_http_class(proxied_host).should be_proxy_class end end end end end end librarian-0.1.1/spec/unit/dsl_spec.rb0000644000004100000410000001327012210667660017547 0ustar www-datawww-datarequire 'librarian' require 'librarian/mock' module Librarian module Mock describe Dsl do let(:env) { Environment.new } context "a single source and a single dependency with a blank name" do it "should not not run with a blank name" do expect do env.dsl do src 'source-1' dep '' end end.to raise_error(ArgumentError, %{name ("") must be sensible}) end end context "a simple specfile - a single source, a single dependency, no transitive dependencies" do it "should run with a hash source" do spec = env.dsl do dep 'dependency-1', :src => 'source-1' end spec.dependencies.should_not be_empty spec.dependencies.first.name.should == 'dependency-1' spec.dependencies.first.source.name.should == 'source-1' spec.sources.should be_empty end it "should run with a shortcut source" do spec = env.dsl do dep 'dependency-1', :source => :a end spec.dependencies.should_not be_empty spec.dependencies.first.name.should == 'dependency-1' spec.dependencies.first.source.name.should == 'source-a' spec.sources.should be_empty end it "should run with a block hash source" do spec = env.dsl do source :src => 'source-1' do dep 'dependency-1' end end spec.dependencies.should_not be_empty spec.dependencies.first.name.should == 'dependency-1' spec.dependencies.first.source.name.should == 'source-1' spec.sources.should be_empty end it "should run with a block named source" do spec = env.dsl do src 'source-1' do dep 'dependency-1' end end spec.dependencies.should_not be_empty spec.dependencies.first.name.should == 'dependency-1' spec.dependencies.first.source.name.should == 'source-1' spec.sources.should be_empty end it "should run with a default hash source" do spec = env.dsl do source :src => 'source-1' dep 'dependency-1' end spec.dependencies.should_not be_empty spec.dependencies.first.name.should == 'dependency-1' spec.dependencies.first.source.name.should == 'source-1' spec.sources.should_not be_empty spec.dependencies.first.source.should == spec.sources.first end it "should run with a default named source" do spec = env.dsl do src 'source-1' dep 'dependency-1' end spec.dependencies.should_not be_empty spec.dependencies.first.name.should == 'dependency-1' spec.dependencies.first.source.name.should == 'source-1' spec.sources.should_not be_empty spec.dependencies.first.source.should == spec.sources.first end it "should run with a default shortcut source" do spec = env.dsl do source :a dep 'dependency-1' end spec.dependencies.should_not be_empty spec.dependencies.first.name.should == 'dependency-1' spec.dependencies.first.source.name.should == 'source-a' spec.sources.should_not be_empty spec.dependencies.first.source.should == spec.sources.first end it "should run with a shortcut source hash definition" do spec = env.dsl do source :b, :src => 'source-b' dep 'dependency-1', :source => :b end spec.dependencies.should_not be_empty spec.dependencies.first.name.should == 'dependency-1' spec.dependencies.first.source.name.should == 'source-b' spec.sources.should be_empty end it "should run with a shortcut source block definition" do spec = env.dsl do source :b, proc { src 'source-b' } dep 'dependency-1', :source => :b end spec.dependencies.should_not be_empty spec.dependencies.first.name.should == 'dependency-1' spec.dependencies.first.source.name.should == 'source-b' spec.sources.should be_empty end it "should run with a default shortcut source hash definition" do spec = env.dsl do source :b, :src => 'source-b' source :b dep 'dependency-1' end spec.dependencies.should_not be_empty spec.dependencies.first.name.should == 'dependency-1' spec.dependencies.first.source.name.should == 'source-b' spec.sources.should_not be_empty spec.sources.first.name.should == 'source-b' end it "should run with a default shortcut source block definition" do spec = env.dsl do source :b, proc { src 'source-b' } source :b dep 'dependency-1' end spec.dependencies.should_not be_empty spec.dependencies.first.name.should == 'dependency-1' spec.dependencies.first.source.name.should == 'source-b' spec.sources.should_not be_empty spec.sources.first.name.should == 'source-b' end end context "validating source options" do it "should raise when given unrecognized optiosn options" do expect do env.dsl do dep 'dependency-1', :src => 'source-1', :huh => 'yikes' end end.to raise_error(Error, %{unrecognized options: huh}) end end end end endlibrarian-0.1.1/spec/unit/manifest_set_spec.rb0000644000004100000410000001202712210667660021445 0ustar www-datawww-datarequire 'librarian' module Librarian describe ManifestSet do describe ".new" do let(:jelly) { double(:name => "jelly") } let(:butter) { double(:name => "butter") } let(:jam) { double(:name => "jam") } let(:array) { [jelly, butter, jam] } let(:hash) { {"jelly" => jelly, "butter" => butter, "jam" => jam} } context "with an array" do let(:set) { described_class.new(array) } it "should give back the array" do set.to_a.should =~ array end it "should give back the hash" do set.to_hash.should == hash end end context "with a hash" do let(:set) { described_class.new(hash) } it "should give back the array" do set.to_a.should =~ array end it "should give back the hash" do set.to_hash.should == hash end end end # Does not trace dependencies. # That's why it's "shallow". describe "#shallow_strip!" do let(:jelly) { double(:name => "jelly") } let(:butter) { double(:name => "butter") } let(:jam) { double(:name => "jam") } let(:set) { described_class.new([jelly, butter, jam]) } it "should not do anything when given no names" do set.shallow_strip!([]) set.to_a.should =~ [jelly, butter, jam] end it "should remove only the named elements" do set.shallow_strip!(["butter", "jam"]) set.to_a.should =~ [jelly] end it "should allow removing all the elements" do set.shallow_strip!(["jelly", "butter", "jam"]) set.to_a.should =~ [] end end # Does not trace dependencies. # That's why it's "shallow". describe "#shallow_keep!" do let(:jelly) { double(:name => "jelly") } let(:butter) { double(:name => "butter") } let(:jam) { double(:name => "jam") } let(:set) { described_class.new([jelly, butter, jam]) } it "should empty the set when given no names" do set.shallow_keep!([]) set.to_a.should =~ [] end it "should keep only the named elements" do set.shallow_keep!(["butter", "jam"]) set.to_a.should =~ [butter, jam] end it "should allow keeping all the elements" do set.shallow_keep!(["jelly", "butter", "jam"]) set.to_a.should =~ [jelly, butter, jam] end end describe "#deep_strip!" do def man(o) k, v = o.keys.first, o.values.first double(k, :name => k, :dependencies => deps(v)) end def deps(names) names.map{|n| double(:name => n)} end let(:a) { man("a" => %w[b c]) } let(:b) { man("b" => %w[c d]) } let(:c) { man("c" => %w[ ]) } let(:d) { man("d" => %w[ ]) } let(:e) { man("e" => %w[f g]) } let(:f) { man("f" => %w[g h]) } let(:g) { man("g" => %w[ ]) } let(:h) { man("h" => %w[ ]) } let(:set) { described_class.new([a, b, c, d, e, f, g, h]) } it "should not do anything when given no names" do set.deep_strip!([]) set.to_a.should =~ [a, b, c, d, e, f, g, h] end it "should remove just the named elements if they have no dependencies" do set.deep_strip!(["c", "h"]) set.to_a.should =~ [a, b, d, e, f, g] end it "should remove the named elements and all their dependencies" do set.deep_strip!(["b"]) set.to_a.should =~ [a, e, f, g, h] end it "should remove an entire tree of dependencies" do set.deep_strip!(["e"]) set.to_a.should =~ [a, b, c, d] end it "should allow removing all the elements" do set.deep_strip!(["a", "e"]) set.to_a.should =~ [] end end describe "#deep_keep!" do def man(o) k, v = o.keys.first, o.values.first double(k, :name => k, :dependencies => deps(v)) end def deps(names) names.map{|n| double(:name => n)} end let(:a) { man("a" => %w[b c]) } let(:b) { man("b" => %w[c d]) } let(:c) { man("c" => %w[ ]) } let(:d) { man("d" => %w[ ]) } let(:e) { man("e" => %w[f g]) } let(:f) { man("f" => %w[g h]) } let(:g) { man("g" => %w[ ]) } let(:h) { man("h" => %w[ ]) } let(:set) { described_class.new([a, b, c, d, e, f, g, h]) } it "should remove all the elements when given no names" do set.deep_keep!([]) set.to_a.should =~ [] end it "should keep just the named elements if they have no dependencies" do set.deep_keep!(["c", "h"]) set.to_a.should =~ [c, h] end it "should keep the named elements and all their dependencies" do set.deep_keep!(["b"]) set.to_a.should =~ [b, c, d] end it "should keep an entire tree of dependencies" do set.deep_keep!(["e"]) set.to_a.should =~ [e, f, g, h] end it "should allow keeping all the elements" do set.deep_keep!(["a", "e"]) set.to_a.should =~ [a, b, c, d, e, f, g, h] end end end end librarian-0.1.1/spec/unit/lockfile/0000755000004100000410000000000012210667660017213 5ustar www-datawww-datalibrarian-0.1.1/spec/unit/lockfile/parser_spec.rb0000644000004100000410000001037112210667660022050 0ustar www-datawww-datarequire "librarian/helpers" require "librarian/lockfile/parser" require "librarian/mock" module Librarian describe Lockfile::Parser do let(:env) { Mock::Environment.new } let(:parser) { described_class.new(env) } let(:resolution) { parser.parse(lockfile) } context "a mock lockfile with one source and no dependencies" do let(:lockfile) do Helpers.strip_heredoc <<-LOCKFILE MOCK remote: source-a specs: DEPENDENCIES LOCKFILE end it "should give an empty list of dependencies" do resolution.dependencies.should be_empty end it "should give an empty list of manifests" do resolution.manifests.should be_empty end end context "a mock lockfile with one source and one dependency" do let(:lockfile) do Helpers.strip_heredoc <<-LOCKFILE MOCK remote: source-a specs: jelly (1.3.5) DEPENDENCIES jelly (!= 1.2.6, ~> 1.1) LOCKFILE end it "should give a list of one dependency" do resolution.should have(1).dependencies end it "should give a dependency with the expected name" do dependency = resolution.dependencies.first dependency.name.should == "jelly" end it "should give a dependency with the expected requirement" do dependency = resolution.dependencies.first # Note: it must be this order because this order is lexicographically sorted. dependency.requirement.to_s.should == "!= 1.2.6, ~> 1.1" end it "should give a dependency wth the expected source" do dependency = resolution.dependencies.first source = dependency.source source.name.should == "source-a" end it "should give a list of one manifest" do resolution.should have(1).manifests end it "should give a manifest with the expected name" do manifest = resolution.manifests.first manifest.name.should == "jelly" end it "should give a manifest with the expected version" do manifest = resolution.manifests.first manifest.version.to_s.should == "1.3.5" end it "should give a manifest with no dependencies" do manifest = resolution.manifests.first manifest.dependencies.should be_empty end it "should give a manifest with the expected source" do manifest = resolution.manifests.first source = manifest.source source.name.should == "source-a" end it "should give the dependency and the manifest the same source instance" do dependency = resolution.dependencies.first manifest = resolution.manifests.first dependency_source = dependency.source manifest_source = manifest.source manifest_source.should be dependency_source end end context "a mock lockfile with one source and a complex dependency" do let(:lockfile) do Helpers.strip_heredoc <<-LOCKFILE MOCK remote: source-a specs: butter (2.5.3) jelly (1.3.5) butter (< 3, >= 1.1) DEPENDENCIES jelly (!= 1.2.6, ~> 1.1) LOCKFILE end it "should give a list of one dependency" do resolution.should have(1).dependencies end it "should have the expected dependency" do dependency = resolution.dependencies.first dependency.name.should == "jelly" end it "should give a list of all the manifests" do resolution.should have(2).manifests end it "should include all the expected manifests" do manifests = ManifestSet.new(resolution.manifests) manifests.to_hash.keys.should =~ %w(butter jelly) end it "should have an internally consistent set of manifests" do manifests = ManifestSet.new(resolution.manifests) manifests.should be_consistent end it "should have an externally consistent set of manifests" do dependencies = resolution.dependencies manifests = ManifestSet.new(resolution.manifests) manifests.should be_in_compliance_with dependencies end end end end librarian-0.1.1/spec/unit/config/0000755000004100000410000000000012210667660016670 5ustar www-datawww-datalibrarian-0.1.1/spec/unit/config/database_spec.rb0000644000004100000410000001704512210667660022002 0ustar www-datawww-datarequire "fileutils" require "pathname" require "tmpdir" require "yaml" require "support/fakefs" require "librarian/config/database" describe Librarian::Config::Database do include ::Support::FakeFS def write_yaml!(path, *yamlables) path = Pathname(path) path.dirname.mkpath unless path.dirname.directory? File.open(path, "wb"){|f| yamlables.each{|y| YAML.dump(y, f)}} end let(:adapter_name) { "gem" } let(:env) { { } } let(:pwd) { Pathname(Dir.tmpdir) } let(:home) { Pathname("~").expand_path } let(:project_path) { nil } let(:specfile_name) { nil } let(:global) { home.join(".librarian/gem/config") } let(:local) { pwd.join(".librarian/gem/config") } let(:specfile) { pwd.join("Gemfile") } before do FileUtils.mkpath(pwd) FileUtils.touch(specfile) end let(:database) do described_class.new(adapter_name, :env => env, :pwd => pwd.to_s, :home => home.to_s, :project_path => project_path, :specfile_name => specfile_name ) end context "when a key is given globally" do let(:key) { "jam" } let(:value) { "jelly" } let(:raw_key) { "LIBRARIAN_GEM_JAM" } before do write_yaml! global, raw_key => value end it "should have the key globally" do database.global[key].should == value end it "should not have the key in the env" do database.env[key].should be_nil end it "should not have the key locally" do database.local[key].should be_nil end it "should have the key generally" do database[key].should == value end end context "when a key is set globally" do let(:key) { "jam" } let(:value) { "jelly" } let(:raw_key) { "LIBRARIAN_GEM_JAM" } before do database.global[key] = value end it "should have the key globally" do database.global[key].should == value end it "should not have the key in the env" do database.env[key].should be_nil end it "should not have the key locally" do database.local[key].should be_nil end it "should have the key generally" do database[key].should == value end it "should persist the key" do data = YAML.load_file(global) data.should == {raw_key => value} end end context "when the key is set and unset globally" do let(:key) { "jam" } let(:value) { "jelly" } let(:raw_key) { "LIBRARIAN_GEM_JAM" } before do database.global[key] = value database.global[key] = nil end it "should not have the key globally" do database.global[key].should be_nil end it "should not have the key in the env" do database.env[key].should be_nil end it "should not have the key locally" do database.local[key].should be_nil end it "should not have the key generally" do database[key].should be_nil end it "should unpersist the key" do File.should_not exist global end end context "when a key is given in the env" do let(:key) { "jam" } let(:value) { "jelly" } let(:raw_key) { "LIBRARIAN_GEM_JAM" } #override let(:env) { {raw_key => value} } it "should not have the key globally" do database.global[key].should be_nil end it "should have the key in the env" do database.env[key].should == value end it "should not have the key locally" do database.local[key].should be_nil end it "should have the key generally" do database[key].should == value end end context "when a key is given locally" do let(:key) { "jam" } let(:value) { "jelly" } let(:raw_key) { "LIBRARIAN_GEM_JAM" } before do write_yaml! local, raw_key => value end it "should not have the key globally" do database.global[key].should be_nil end it "should not have the key in the env" do database.env[key].should be_nil end it "should have the key locally" do database.local[key].should == value end it "should have the key generally" do database[key].should == value end end context "when a key is set locally" do let(:key) { "jam" } let(:value) { "jelly" } let(:raw_key) { "LIBRARIAN_GEM_JAM" } before do database.local[key] = value end it "should not have the key globally" do database.global[key].should be_nil end it "should not have the key in the env" do database.env[key].should be_nil end it "should have the key locally" do database.local[key].should == value end it "should have the key generally" do database[key].should == value end it "should persist the key" do data = YAML.load_file(local) data.should == {raw_key => value} end end context "when the key is set and unset locally" do let(:key) { "jam" } let(:value) { "jelly" } let(:raw_key) { "LIBRARIAN_GEM_JAM" } before do database.local[key] = value database.local[key] = nil end it "should not have the key globally" do database.global[key].should be_nil end it "should not have the key in the env" do database.env[key].should be_nil end it "should not have the key locally" do database.local[key].should be_nil end it "should not have the key generally" do database[key].should be_nil end it "should unpersist the key" do File.should_not exist local end end context "setting malformatted keys" do it "should ban caps" do expect { database.global["JAM"] = "jelly" }. to raise_error Librarian::Error, %[key not permitted: "JAM"] end it "should ban double dots" do expect { database.global["jam..jam"] = "jelly" }. to raise_error Librarian::Error, %[key not permitted: "jam..jam"] end end context "setting banned keys" do it "should ban the specfile key" do expect { database.global["gemfile"] = "jelly" }. to raise_error Librarian::Error, %[key not permitted: "gemfile"] end it "should ban the global-config key" do expect { database.global["config"] = "jelly" }. to raise_error Librarian::Error, %[key not permitted: "config"] end end context "project_path" do context "by default" do it "should give the default project path" do database.project_path.should == Pathname("/tmp") end end context "when the specfile is set in the env" do let(:env) { {"LIBRARIAN_GEM_GEMFILE" => "/non/sense/path/to/Sillyfile"} } it "should give the project path from the env-set specfile" do database.project_path.should == Pathname("/non/sense/path/to") end end end context "specfile_path" do context "by default" do it "should give the default specfile path" do database.specfile_path.should == specfile end end context "when set in the env" do let(:env) { {"LIBRARIAN_GEM_GEMFILE" => "/non/sense/path/to/Sillyfile"} } it "should give the given specfile path" do database.specfile_path.should == Pathname("/non/sense/path/to/Sillyfile") end end context "when the project_path is assigned" do let(:project_path) { "/non/sense/path/to" } it "should give the assigned specfile path" do database.specfile_path.should == Pathname("/non/sense/path/to/Gemfile") end end context "when the specfile_name is assigned" do let(:specfile_name) { "Sillyfile" } it "should give the assigned specfile path" do database.specfile_path.should == Pathname("/tmp/Sillyfile") end end end end librarian-0.1.1/spec/unit/manifest_spec.rb0000644000004100000410000000160712210667660020574 0ustar www-datawww-datarequire "librarian/manifest" describe Librarian::Manifest do describe "validations" do context "when the name is blank" do it "raises" do expect { described_class.new(nil, "") }. to raise_error(ArgumentError, %{name ("") must be sensible}) end end context "when the name has leading whitespace" do it "raises" do expect { described_class.new(nil, " the-name") }. to raise_error(ArgumentError, %{name (" the-name") must be sensible}) end end context "when the name has trailing whitespace" do it "raises" do expect { described_class.new(nil, "the-name ") }. to raise_error(ArgumentError, %{name ("the-name ") must be sensible}) end end context "when the name is a single character" do it "passes" do described_class.new(nil, "R") end end end end librarian-0.1.1/spec/unit/action/0000755000004100000410000000000012210667660016700 5ustar www-datawww-datalibrarian-0.1.1/spec/unit/action/ensure_spec.rb0000644000004100000410000000152212210667660021540 0ustar www-datawww-datarequire "tmpdir" require "librarian/error" require "librarian/action/ensure" module Librarian describe Action::Ensure do let(:env) { double } let(:action) { described_class.new(env) } before do env.stub(:specfile_name) { "Specfile" } end describe "#run" do context "when the environment does not know its project path" do before { env.stub(:project_path) { nil } } it "should raise an error describing that the specfile is mising" do expect { action.run }.to raise_error(Error, "Cannot find Specfile!") end end context "when the environment knows its project path" do before { env.stub(:project_path) { Dir.tmpdir } } it "should not raise an error" do expect { action.run }.to_not raise_error end end end end end librarian-0.1.1/spec/unit/action/install_spec.rb0000644000004100000410000000674112210667660021715 0ustar www-datawww-datarequire "librarian/error" require "librarian/action/install" module Librarian describe Action::Install do let(:env) { double(:specfile_name => "Specfile", :lockfile_name => "Specfile.lock") } let(:action) { described_class.new(env) } describe "#run" do describe "behavior" do describe "checking preconditions" do context "when the specfile is missing" do before do env.stub_chain(:specfile_path, :exist?) { false } end it "should raise an error explaining that the specfile is missing" do expect { action.run }.to raise_error(Error, "Specfile missing!") end end context "when the specfile is present but the lockfile is missing" do before do env.stub_chain(:specfile_path, :exist?) { true } env.stub_chain(:lockfile_path, :exist?) { false } end it "should raise an error explaining that the lockfile is missing" do expect { action.run }.to raise_error(Error, "Specfile.lock missing!") end end context "when the specfile and lockfile are present but inconsistent" do before do env.stub_chain(:specfile_path, :exist?) { true } env.stub_chain(:lockfile_path, :exist?) { true } action.stub(:spec_consistent_with_lock?) { false } end it "should raise an error explaining the inconsistenty" do expect { action.run }.to raise_error(Error, "Specfile and Specfile.lock are out of sync!") end end context "when the specfile and lockfile are present and consistent" do before do env.stub_chain(:specfile_path, :exist?) { true } env.stub_chain(:lockfile_path, :exist?) { true } action.stub(:spec_consistent_with_lock?) { true } action.stub(:perform_installation) end it "should not raise an error" do expect { action.run }.to_not raise_error end end end describe "performing the install" do def mock_manifest(i) double(:name => "manifest-#{i}") end let(:manifests) { 3.times.map{|i| mock_manifest(i)} } let(:sorted_manifests) { 4.times.map{|i| mock_manifest(i + 3)} } let(:install_path) { double } before do env.stub(:install_path) { install_path } action.stub(:check_preconditions) action.stub_chain(:lock, :manifests) { manifests } end after do action.run end it "should sort and install the manifests" do ManifestSet.should_receive(:sort).with(manifests).exactly(:once).ordered { sorted_manifests } install_path.stub(:exist?) { false } install_path.should_receive(:mkpath).exactly(:once).ordered sorted_manifests.each do |manifest| manifest.should_receive(:install!).exactly(:once).ordered end end it "should recreate the install path if it already exists" do action.stub(:sorted_manifests) { sorted_manifests } action.stub(:install_manifests) install_path.stub(:exist?) { true } install_path.should_receive(:rmtree) install_path.should_receive(:mkpath) end end end end end end librarian-0.1.1/spec/unit/action/base_spec.rb0000644000004100000410000000052312210667660021151 0ustar www-datawww-datarequire "librarian/action/base" module Librarian describe Action::Base do let(:env) { double } let(:action) { described_class.new(env) } subject { action } it { should respond_to :environment } it "should have the environment that was assigned to it" do action.environment.should be env end end end librarian-0.1.1/spec/unit/action/clean_spec.rb0000644000004100000410000000514512210667660021326 0ustar www-datawww-datarequire "librarian/action/clean" module Librarian describe Action::Clean do let(:env) { double } let(:action) { described_class.new(env) } before do action.stub(:debug) end describe "#run" do describe "behavior" do after do action.run end describe "clearing the cache path" do before do action.stub(:clean_install_path) end context "when the cache path is missing" do before do env.stub_chain(:cache_path, :exist?) { false } end it "should not try to clear the cache path" do env.cache_path.should_receive(:rmtree).never end end context "when the cache path is present" do before do env.stub_chain(:cache_path, :exist?) { true } end it "should try to clear the cache path" do env.cache_path.should_receive(:rmtree).exactly(:once) end end end describe "clearing the install path" do before do action.stub(:clean_cache_path) end context "when the install path is missing" do before do env.stub_chain(:install_path, :exist?) { false } end it "should not try to clear the install path" do env.install_path.should_receive(:children).never end end context "when the install path is present" do before do env.stub_chain(:install_path, :exist?) { true } end it "should try to clear the install path" do children = [double, double, double] children.each do |child| child.stub(:file?) { false } end env.stub_chain(:install_path, :children) { children } children.each do |child| child.should_receive(:rmtree).exactly(:once) end end it "should only try to clear out directories from the install path, not files" do children = [double(:file? => false), double(:file? => true), double(:file? => true)] env.stub_chain(:install_path, :children) { children } children.select(&:file?).each do |child| child.should_receive(:rmtree).never end children.reject(&:file?).each do |child| child.should_receive(:rmtree).exactly(:once) end end end end end end end end librarian-0.1.1/spec/unit/dependency_spec.rb0000644000004100000410000001313012210667660021076 0ustar www-datawww-datarequire "librarian/dependency" describe Librarian::Dependency do describe "validations" do context "when the name is blank" do it "raises" do expect { described_class.new("", [], nil) }. to raise_error(ArgumentError, %{name ("") must be sensible}) end end context "when the name has leading whitespace" do it "raises" do expect { described_class.new(" the-name", [], nil) }. to raise_error(ArgumentError, %{name (" the-name") must be sensible}) end end context "when the name has trailing whitespace" do it "raises" do expect { described_class.new("the-name ", [], nil) }. to raise_error(ArgumentError, %{name ("the-name ") must be sensible}) end end context "when the name is a single character" do it "passes" do described_class.new("R", [], nil) end end end describe "#consistent_with?" do def req(s) described_class::Requirement.new(s) end def self.assert_consistent(a, b) /^(.+):(\d+):in `(.+)'$/ =~ caller.first line = $2.to_i title = "is consistent with #{a.inspect} and #{b.inspect}" module_eval <<-CODE, __FILE__, line it #{title.inspect} do a, b = req(#{a.inspect}), req(#{b.inspect}) expect(a).to be_consistent_with(b) expect(a).to_not be_inconsistent_with(b) expect(b).to be_consistent_with(a) expect(b).to_not be_inconsistent_with(a) end CODE end def self.refute_consistent(a, b) /^(.+):(\d+):in `(.+)'$/ =~ caller.first line = $2.to_i title = "is inconsistent with #{a.inspect} and #{b.inspect}" module_eval <<-CODE, __FILE__, line it #{title.inspect} do a, b = req(#{a.inspect}), req(#{b.inspect}) expect(a).to_not be_consistent_with(b) expect(a).to be_inconsistent_with(b) expect(b).to_not be_consistent_with(a) expect(b).to be_inconsistent_with(a) end CODE end # = = assert_consistent "3", "3" refute_consistent "3", "4" refute_consistent "3", "0" refute_consistent "0", "3" # = != assert_consistent "3", "!= 4" assert_consistent "3", "!= 0" refute_consistent "3", "!= 3" # = > assert_consistent "3", "> 2" refute_consistent "3", "> 3" refute_consistent "3", "> 4" # = < assert_consistent "3", "< 4" refute_consistent "3", "< 3" refute_consistent "3", "< 2" # = >= assert_consistent "3", ">= 2" assert_consistent "3", ">= 3" refute_consistent "3", ">= 4" # = <= assert_consistent "3", "<= 4" assert_consistent "3", "<= 3" refute_consistent "3", "<= 2" # = ~> assert_consistent "3.4.1", "~> 3.4.1" assert_consistent "3.4.2", "~> 3.4.1" refute_consistent "3.4", "~> 3.4.1" refute_consistent "3.5", "~> 3.4.1" # != != assert_consistent "!= 3", "!= 3" assert_consistent "!= 3", "!= 4" # != > assert_consistent "!= 3", "> 2" assert_consistent "!= 3", "> 3" assert_consistent "!= 3", "> 4" # != < assert_consistent "!= 3", "< 2" assert_consistent "!= 3", "< 3" assert_consistent "!= 3", "< 4" # != >= assert_consistent "!= 3", ">= 2" assert_consistent "!= 3", ">= 3" assert_consistent "!= 3", ">= 4" # != <= assert_consistent "!= 3", "<= 2" assert_consistent "!= 3", "<= 3" assert_consistent "!= 3", "<= 4" # != ~> assert_consistent "!= 3.4.1", "~> 3.4.1" assert_consistent "!= 3.4.2", "~> 3.4.1" assert_consistent "!= 3.5", "~> 3.4.1" # > > assert_consistent "> 3", "> 2" assert_consistent "> 3", "> 3" assert_consistent "> 3", "> 4" # > < assert_consistent "> 3", "< 4" refute_consistent "> 3", "< 3" refute_consistent "> 3", "< 2" # > >= assert_consistent "> 3", ">= 2" assert_consistent "> 3", ">= 3" assert_consistent "> 3", ">= 4" # > <= assert_consistent "> 3", "<= 4" refute_consistent "> 3", "<= 3" refute_consistent "> 3", "<= 2" # > ~> assert_consistent "> 3.3", "~> 3.4.1" assert_consistent "> 3.4.1", "~> 3.4.1" assert_consistent "> 3.4.2", "~> 3.4.1" refute_consistent "> 3.5", "~> 3.4.1" # < < assert_consistent "< 3", "< 2" assert_consistent "< 3", "< 3" assert_consistent "< 3", "< 4" # < >= assert_consistent "< 3", ">= 2" refute_consistent "< 3", ">= 3" refute_consistent "< 3", ">= 4" # < <= assert_consistent "< 3", "<= 2" assert_consistent "< 3", "<= 3" assert_consistent "< 3", "<= 4" # >= >= assert_consistent ">= 3", ">= 2" assert_consistent ">= 3", ">= 3" assert_consistent ">= 3", ">= 4" # >= <= assert_consistent ">= 3", "<= 4" assert_consistent ">= 3", "<= 3" refute_consistent ">= 3", "<= 2" # >= ~> assert_consistent ">= 3.3", "~> 3.4.1" assert_consistent ">= 3.4.1", "~> 3.4.1" assert_consistent ">= 3.4.2", "~> 3.4.1" refute_consistent ">= 3.5", "~> 3.4.1" # <= <= assert_consistent "<= 3", "<= 2" assert_consistent "<= 3", "<= 3" assert_consistent "<= 3", "<= 4" # <= ~> assert_consistent "<= 3.5", "~> 3.4.1" assert_consistent "<= 3.4.1", "~> 3.4.1" assert_consistent "<= 3.4.2", "~> 3.4.1" refute_consistent "<= 3.3", "~> 3.4.1" # ~> ~> assert_consistent "~> 3.4.1", "~> 3.4.1" assert_consistent "~> 3.4.2", "~> 3.4.1" assert_consistent "~> 3.3", "~> 3.4.1" refute_consistent "~> 3.3.3", "~> 3.4.1" refute_consistent "~> 3.5", "~> 3.4.1" refute_consistent "~> 3.5.4", "~> 3.4.1" end end librarian-0.1.1/spec/support/0000755000004100000410000000000012210667660016160 5ustar www-datawww-datalibrarian-0.1.1/spec/support/with_env_macro.rb0000644000004100000410000000047612210667660021520 0ustar www-datawww-datamodule Support module WithEnvMacro module ClassMethods def with_env(new) old = Hash[new.map{|k, v| [k, ENV[k]]}] before { ENV.update(new) } after { ENV.update(old) } end end private def self.included(base) base.extend(ClassMethods) end end end librarian-0.1.1/spec/support/method_patch_macro.rb0000644000004100000410000000115012210667660022322 0ustar www-datawww-datarequire "securerandom" module Support module MethodPatchMacro def self.included(base) base.extend ClassMethods end module ClassMethods def with_module_method(mod, meth, &block) tag = SecureRandom.hex(8) orig_meth = "_#{tag}_#{meth}".to_sym before do mod.module_eval do alias_method orig_meth, meth define_method meth, &block end end after do mod.module_eval do alias_method meth, orig_meth remove_method orig_meth end end end end end end librarian-0.1.1/spec/support/fakefs.rb0000644000004100000410000000164612210667660017753 0ustar www-datawww-datarequire "fakefs/safe" require "fakefs/spec_helpers" require "support/method_patch_macro" if defined?(Rubinius) module Rubinius class CodeLoader class << self alias_method :require_fakefs_original, :require def require(s) ::FakeFS.without { require_fakefs_original(s) } end end end end end module Support module FakeFS def self.included(base) base.module_exec do include ::FakeFS::SpecHelpers end # Since ruby-1.9.3-p286, Kernel#Pathname was changed in a way that broke # FakeFS's assumptions. It used to lookup the Pathname constant (which is # where FakeFS hooks) and send it #new, but now it keeps a reference to # the Pathname constant (breaking the FakeFS hook). base.module_exec do include MethodPatchMacro with_module_method(Kernel, :Pathname){|s| Pathname.new(s)} end end end end librarian-0.1.1/.rspec0000644000004100000410000000001012210667660014616 0ustar www-datawww-data--color librarian-0.1.1/LICENSE.txt0000644000004100000410000000207112210667660015335 0ustar www-datawww-dataCopyright (c) 2011 ApplicationsOnline, LLC. 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. librarian-0.1.1/checksums.yaml.gz0000444000004100000410000000041712210667660017002 0ustar www-datawww-data‹IÙRe9RdA Dý>E_ '$•J >'Pik¢-NÏLp3ôrÑãñ¸½¿½âËí~ÿ×ϨxÆŸ¿Ÿ/÷2GÓ’f›Nž l ˜©b÷Í<ãã››U«JÎL0.ñ£§ˆ8(|Õž^È“ö?u#ýÊ=û€x-èƒe¢¥£Y¾ ¸¶‚ª4w뀇$×V:0ë$m ™½èÒÄÄ×:1]Ð.ÄÚ´N»C²‡æuà'‘‰±*½~îJË}h¡Ö"4ˤæaÝ“­<>s}æª - !ruby/object:Gem::Version version: '0.15' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: '0.15' - !ruby/object:Gem::Dependency name: highline requirement: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: rake requirement: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: rspec requirement: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: json requirement: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: fakefs requirement: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: 0.4.2 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: 0.4.2 description: A Framework for Bundlers. email: - y_feldblum@yahoo.com executables: [] extensions: [] extra_rdoc_files: [] files: - .gitignore - .rspec - .travis.yml - CHANGELOG.md - Gemfile - LICENSE.txt - README.md - Rakefile - lib/librarian.rb - lib/librarian/action.rb - lib/librarian/action/base.rb - lib/librarian/action/clean.rb - lib/librarian/action/ensure.rb - lib/librarian/action/install.rb - lib/librarian/action/persist_resolution_mixin.rb - lib/librarian/action/resolve.rb - lib/librarian/action/update.rb - lib/librarian/cli.rb - lib/librarian/cli/manifest_presenter.rb - lib/librarian/config.rb - lib/librarian/config/database.rb - lib/librarian/config/file_source.rb - lib/librarian/config/hash_source.rb - lib/librarian/config/source.rb - lib/librarian/dependency.rb - lib/librarian/dsl.rb - lib/librarian/dsl/receiver.rb - lib/librarian/dsl/target.rb - lib/librarian/environment.rb - lib/librarian/error.rb - lib/librarian/helpers.rb - lib/librarian/linter/source_linter.rb - lib/librarian/lockfile.rb - lib/librarian/lockfile/compiler.rb - lib/librarian/lockfile/parser.rb - lib/librarian/logger.rb - lib/librarian/manifest.rb - lib/librarian/manifest_set.rb - lib/librarian/mock.rb - lib/librarian/mock/cli.rb - lib/librarian/mock/dsl.rb - lib/librarian/mock/environment.rb - lib/librarian/mock/extension.rb - lib/librarian/mock/source.rb - lib/librarian/mock/source/mock.rb - lib/librarian/mock/source/mock/registry.rb - lib/librarian/mock/version.rb - lib/librarian/posix.rb - lib/librarian/resolution.rb - lib/librarian/resolver.rb - lib/librarian/resolver/implementation.rb - lib/librarian/rspec/support/cli_macro.rb - lib/librarian/source.rb - lib/librarian/source/basic_api.rb - lib/librarian/source/git.rb - lib/librarian/source/git/repository.rb - lib/librarian/source/local.rb - lib/librarian/source/path.rb - lib/librarian/spec.rb - lib/librarian/spec_change_set.rb - lib/librarian/specfile.rb - lib/librarian/support/abstract_method.rb - lib/librarian/ui.rb - lib/librarian/version.rb - librarian.gemspec - spec/functional/cli_spec.rb - spec/functional/posix_spec.rb - spec/functional/source/git/repository_spec.rb - spec/functional/source/git_spec.rb - spec/support/fakefs.rb - spec/support/method_patch_macro.rb - spec/support/with_env_macro.rb - spec/unit/action/base_spec.rb - spec/unit/action/clean_spec.rb - spec/unit/action/ensure_spec.rb - spec/unit/action/install_spec.rb - spec/unit/config/database_spec.rb - spec/unit/dependency_spec.rb - spec/unit/dsl_spec.rb - spec/unit/environment_spec.rb - spec/unit/lockfile/parser_spec.rb - spec/unit/lockfile_spec.rb - spec/unit/manifest_set_spec.rb - spec/unit/manifest_spec.rb - spec/unit/mock/environment_spec.rb - spec/unit/mock/source/mock_spec.rb - spec/unit/resolver_spec.rb - spec/unit/source/git_spec.rb - spec/unit/spec_change_set_spec.rb homepage: '' licenses: [] metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.0.3 signing_key: specification_version: 4 summary: A Framework for Bundlers. test_files: - spec/functional/cli_spec.rb - spec/functional/posix_spec.rb - spec/functional/source/git/repository_spec.rb - spec/functional/source/git_spec.rb - spec/support/fakefs.rb - spec/support/method_patch_macro.rb - spec/support/with_env_macro.rb - spec/unit/action/base_spec.rb - spec/unit/action/clean_spec.rb - spec/unit/action/ensure_spec.rb - spec/unit/action/install_spec.rb - spec/unit/config/database_spec.rb - spec/unit/dependency_spec.rb - spec/unit/dsl_spec.rb - spec/unit/environment_spec.rb - spec/unit/lockfile/parser_spec.rb - spec/unit/lockfile_spec.rb - spec/unit/manifest_set_spec.rb - spec/unit/manifest_spec.rb - spec/unit/mock/environment_spec.rb - spec/unit/mock/source/mock_spec.rb - spec/unit/resolver_spec.rb - spec/unit/source/git_spec.rb - spec/unit/spec_change_set_spec.rb librarian-0.1.1/CHANGELOG.md0000644000004100000410000001664212210667660015334 0ustar www-datawww-data# Change Log ## 0.1.1 * \#147. Handle the case where HOME is not in the environment. Thanks @bradleyd. * \#140. Properly handle the failure to clone a git repository. Avoid wiping out the host project's changes. * \#127. Support Rubinius and JRuby, including when shelling out to system commands. * \#144. Allow running the test suite with a simple `rake`. Thanks @nickhammond. ## 0.1.0 * Extract the chef adapter to its own gem. ## 0.0.26 * \#112. Prevent the git source being confused in certain cases by colorized output from git. HT: @ericpp. * \#115, \#116. Accommodate cookbook archives generated by `git archive`. HT: @taqtiqa-mark. * \#119. Support NO_PROXY, a modifier on HTTP_PROXY and friends. HT: @databus23. * \#121. A github pseudosource. HT: @alno. * \#122. Follow http redirect responses in the chef site source. HT: @Fleurer. * Improve the resolver performance by detecting conflicts earlier and backtracking more quickly. This will help avoid certain cases of long resolutions caused by conflicts; the conflicts will still be there, but will be detected more quickly and the resolution terminated more quickly. This changes the resolution algorithm, and new resolutions may differ from older resolutions. ## 0.0.25 * \#71. Fix an error, given certain locale settings, with reading cookbook metadata files. * \#89. Fix an error when encountering manifests and dependencies with names of a single letter, such as `"R"`. * \#92, \#99. HTTP proxy support via the `HTTP_PROXY` environment variable. Also supports `HTTP_PROXY_USER` and `HTTP_PROXY_PASS` environment variables. Thanks @tknerr. * \#97. Enforce that the `:ref` option in the `:git` source may only be given a string. * \#98. Fix unpacking chef site-sourced packages where the directory in the tarball does not match the cookbook name. ## 0.0.24 * \#15. A remembered configuration system. * \#16. Configure, and remember configuration for, chef install paths. * \#38. Configure, and remember configuration for, stripping out `.git` directories from git-sources chef dependencies. * \#76. Support git annotated tags. * \#80. Ignore directories in the `PATH` named `git` when looking for the `git` bin. Thanks @avit. * \#85. Provide a helpful message when running the `show` command without a lockfile present. ## 0.0.23 * \#41. Build gems with a built gemspec. * \#67. Cache remote objects at the latest possible moments, and only when they are needed. * \#68. Fix unpacking chef site-sourced packages on Windows by pivoting from a Librarian-managed scratch space, rather than pivoting from Windows' temp directory. There were unexplained problems with using the Windows temp directory in certain cases, possibly related to the temp directory and the Librarian cache directory being on different volumes. * \#69. Fix invoking Librarian with git-sourced dependencies from git hooks by unsetting `GIT_DIR` around shelling out to git. * Print general environment information when running with `--verbose`. ## 0.0.22 * Fix the `outdated` CLI command. ## 0.0.21 * \#64. Sources now raise when given unrecognized options in the specfile. * A new `show` CLI command. * Changed the `clean` CLI command no longer to delete the lockfile. * Simplify the architecture of `Librarian::Manifest` vis-a-vis sources. It is no longer a base class for adapters to inherit. Now, sources expose a small interface, which `Librarian::Manifest` can call, for delay-loading attributes. * The git source now resolves the `git` binary before `chdir`ing. * Test on Rubinius. ## 0.0.20 * A command to print outdated dependencies. ## 0.0.19 * Fix breakage on 1.8. ## 0.0.18 * \#57. Include existing manifests' dependencies in resolution. * Permit the update action even with a changed specfile. ## 0.0.17 * Use a pure-Ruby implementation of tar/gz. Helps with Windows support, since Windows boxes are less likely than *NIX boxes to have the `tar` executable. * Fix an issue where the chef site source considers uncached manifests to be cached, and skips caching them, causing the install action to fail. * Fail fast if the resolver produces an inconsistent resolution. It is a known issue that the resolver will sometimes (deterministically, not randomly) produce an inconsistent resolution when performing an update action (#57). Start debugging this by failing fast at this point and spitting out gobs of debug-level log details. ## 0.0.16 * Recache site-sourced dependency metadata per each run. * \#46. Always install. * \#53. Abstract versions & requirements from rubygems. * Own the install path. Recreate it on each install. WARNING: If you have your own content in the install path, it will be deleted without mercy. ## 0.0.15 * Rewrite the README. * \#44, \#49. Better updating of cached git sources without using local merges. ## 0.0.14 * \#39 Fixes a regression induced by using `git reset --hard SHA`. ## 0.0.13 * \#36 Fixes an issue where, if a dependency has a git source (the upstream), and the upstream is updated, then attempting to update that dependency in the local resolution would not update that dependency so long as that git source were cached (@databus23). * More immediate detection of, and better error messages for, cases of blank dependency or manifest names or cases of names that are otherwise insensible, such as names that are untrimmed. ## 0.0.12 * Fixes an issue where, if a dependency has a git source with a ref, re-resolving may fail. ## 0.0.11 * Fix a regression where the cli command "version" failed. ## 0.0.10 This release focuses on refactoring some of the internals. There are no functional changes. ## 0.0.9 * \#11 Fixes a problem where, if the repo is in a path where a component has a space, attempting to resolve a site-sourced dependency fails. ## 0.0.8 * A `version` task. * A change log. * \#10 Fixes the problem with bouncing the lockfile when updating, when using a git source with the default ref. ## 0.0.7 * \#8 Add highline temporarily as a runtime dependency of `librarian` (@fnichol). When the project is split into `librarian` and `librarian-chef`, the `chef` runtime dependency will be moved to `librarian-chef`. If the project is further split into a knife plugin, the dependency will be moved there. ## 0.0.6 * \#7 Show a better error message when a cookbook is missing the required metadata file. * Miscellaneous bugfixes. ## 0.0.5 * \#4 An `init` task for `librarian-chef`. This task creates a nearly-blank `Cheffile` with just the default Opscode Community Site source. * Automatically create the `cookbooks` directory, if it's missing, when running the `install` task. * \#3 Add `chef` temporarily as a runtime dependency of `librarian` (@fnichol). When the project is split into `librarian` and `librarian-chef`, the `chef` runtime dependency will be moved to `librarian-chef`. ## 0.0.4 * A simple knife integration. This integration allows you to specify a `librarian-chef`-managed tempdir as a `cookbook_path`. This is useful to force knife only to upload the exact cookbooks that `librarian-chef` knows about, rather than whatever happens to be found in the `cookbooks` directory. ## 0.0.3 * Miscellaneous bugfixes. ## 0.0.2 * An optional `:path` attribute for `:git` sources. This allows you to specify exactly where within a git repository a given cookbook is to be found. * A full example of a Cheffile and its usage in the readme. ## 0.0.1 * Initial release. librarian-0.1.1/Gemfile0000644000004100000410000000015312210667660015004 0ustar www-datawww-datasource "http://rubygems.org" # Specify your gem's dependencies in librarian.gemspec gemspec gem "fakefs" librarian-0.1.1/librarian.gemspec0000644000004100000410000001205512210667660017025 0ustar www-datawww-data# -*- encoding: utf-8 -*- Gem::Specification.new do |s| s.name = "librarian" s.version = "0.1.1" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Jay Feldblum"] s.date = "2013-08-17" s.description = "A Framework for Bundlers." s.email = ["y_feldblum@yahoo.com"] s.files = [".gitignore", ".rspec", ".travis.yml", "CHANGELOG.md", "Gemfile", "LICENSE.txt", "README.md", "Rakefile", "lib/librarian.rb", "lib/librarian/action.rb", "lib/librarian/action/base.rb", "lib/librarian/action/clean.rb", "lib/librarian/action/ensure.rb", "lib/librarian/action/install.rb", "lib/librarian/action/persist_resolution_mixin.rb", "lib/librarian/action/resolve.rb", "lib/librarian/action/update.rb", "lib/librarian/cli.rb", "lib/librarian/cli/manifest_presenter.rb", "lib/librarian/config.rb", "lib/librarian/config/database.rb", "lib/librarian/config/file_source.rb", "lib/librarian/config/hash_source.rb", "lib/librarian/config/source.rb", "lib/librarian/dependency.rb", "lib/librarian/dsl.rb", "lib/librarian/dsl/receiver.rb", "lib/librarian/dsl/target.rb", "lib/librarian/environment.rb", "lib/librarian/error.rb", "lib/librarian/helpers.rb", "lib/librarian/linter/source_linter.rb", "lib/librarian/lockfile.rb", "lib/librarian/lockfile/compiler.rb", "lib/librarian/lockfile/parser.rb", "lib/librarian/logger.rb", "lib/librarian/manifest.rb", "lib/librarian/manifest_set.rb", "lib/librarian/mock.rb", "lib/librarian/mock/cli.rb", "lib/librarian/mock/dsl.rb", "lib/librarian/mock/environment.rb", "lib/librarian/mock/extension.rb", "lib/librarian/mock/source.rb", "lib/librarian/mock/source/mock.rb", "lib/librarian/mock/source/mock/registry.rb", "lib/librarian/mock/version.rb", "lib/librarian/posix.rb", "lib/librarian/resolution.rb", "lib/librarian/resolver.rb", "lib/librarian/resolver/implementation.rb", "lib/librarian/rspec/support/cli_macro.rb", "lib/librarian/source.rb", "lib/librarian/source/basic_api.rb", "lib/librarian/source/git.rb", "lib/librarian/source/git/repository.rb", "lib/librarian/source/local.rb", "lib/librarian/source/path.rb", "lib/librarian/spec.rb", "lib/librarian/spec_change_set.rb", "lib/librarian/specfile.rb", "lib/librarian/support/abstract_method.rb", "lib/librarian/ui.rb", "lib/librarian/version.rb", "librarian.gemspec", "spec/functional/cli_spec.rb", "spec/functional/posix_spec.rb", "spec/functional/source/git/repository_spec.rb", "spec/functional/source/git_spec.rb", "spec/support/fakefs.rb", "spec/support/method_patch_macro.rb", "spec/support/with_env_macro.rb", "spec/unit/action/base_spec.rb", "spec/unit/action/clean_spec.rb", "spec/unit/action/ensure_spec.rb", "spec/unit/action/install_spec.rb", "spec/unit/config/database_spec.rb", "spec/unit/dependency_spec.rb", "spec/unit/dsl_spec.rb", "spec/unit/environment_spec.rb", "spec/unit/lockfile/parser_spec.rb", "spec/unit/lockfile_spec.rb", "spec/unit/manifest_set_spec.rb", "spec/unit/manifest_spec.rb", "spec/unit/mock/environment_spec.rb", "spec/unit/mock/source/mock_spec.rb", "spec/unit/resolver_spec.rb", "spec/unit/source/git_spec.rb", "spec/unit/spec_change_set_spec.rb"] s.homepage = "" s.require_paths = ["lib"] s.rubygems_version = "2.0.3" s.summary = "A Framework for Bundlers." s.test_files = ["spec/functional/cli_spec.rb", "spec/functional/posix_spec.rb", "spec/functional/source/git/repository_spec.rb", "spec/functional/source/git_spec.rb", "spec/support/fakefs.rb", "spec/support/method_patch_macro.rb", "spec/support/with_env_macro.rb", "spec/unit/action/base_spec.rb", "spec/unit/action/clean_spec.rb", "spec/unit/action/ensure_spec.rb", "spec/unit/action/install_spec.rb", "spec/unit/config/database_spec.rb", "spec/unit/dependency_spec.rb", "spec/unit/dsl_spec.rb", "spec/unit/environment_spec.rb", "spec/unit/lockfile/parser_spec.rb", "spec/unit/lockfile_spec.rb", "spec/unit/manifest_set_spec.rb", "spec/unit/manifest_spec.rb", "spec/unit/mock/environment_spec.rb", "spec/unit/mock/source/mock_spec.rb", "spec/unit/resolver_spec.rb", "spec/unit/source/git_spec.rb", "spec/unit/spec_change_set_spec.rb"] if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_runtime_dependency(%q, ["~> 0.15"]) s.add_runtime_dependency(%q, [">= 0"]) s.add_development_dependency(%q, [">= 0"]) s.add_development_dependency(%q, [">= 0"]) s.add_development_dependency(%q, [">= 0"]) s.add_development_dependency(%q, ["~> 0.4.2"]) else s.add_dependency(%q, ["~> 0.15"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, ["~> 0.4.2"]) end else s.add_dependency(%q, ["~> 0.15"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, ["~> 0.4.2"]) end end librarian-0.1.1/.gitignore0000644000004100000410000000005412210667660015501 0ustar www-datawww-data*.gem .bundle Gemfile.lock pkg/* tmp vendor librarian-0.1.1/lib/0000755000004100000410000000000012210667660014260 5ustar www-datawww-datalibrarian-0.1.1/lib/librarian.rb0000644000004100000410000000022612210667660016550 0ustar www-datawww-datarequire 'librarian/version' require 'librarian/environment' module Librarian extend self def environment_class self::Environment end end librarian-0.1.1/lib/librarian/0000755000004100000410000000000012210667660016223 5ustar www-datawww-datalibrarian-0.1.1/lib/librarian/dependency.rb0000644000004100000410000000705112210667660020671 0ustar www-datawww-datarequire 'rubygems' module Librarian class Dependency class Requirement def initialize(*args) args = initialize_normalize_args(args) self.backing = Gem::Requirement.create(*args) end def to_gem_requirement backing end def satisfied_by?(version) to_gem_requirement.satisfied_by?(version.to_gem_version) end def ==(other) to_gem_requirement == other.to_gem_requirement end def to_s to_gem_requirement.to_s end COMPATS_TABLE = { %w(= = ) => lambda{|s, o| s == o}, %w(= !=) => lambda{|s, o| s != o}, %w(= > ) => lambda{|s, o| s > o}, %w(= < ) => lambda{|s, o| s < o}, %w(= >=) => lambda{|s, o| s >= o}, %w(= <=) => lambda{|s, o| s <= o}, %w(= ~>) => lambda{|s, o| s >= o && s.release < o.bump}, %w(!= !=) => true, %w(!= > ) => true, %w(!= < ) => true, %w(!= >=) => true, %w(!= <=) => true, %w(!= ~>) => true, %w(> > ) => true, %w(> < ) => lambda{|s, o| s < o}, %w(> >=) => true, %w(> <=) => lambda{|s, o| s < o}, %w(> ~>) => lambda{|s, o| s < o.bump}, %w(< < ) => true, %w(< >=) => lambda{|s, o| s > o}, %w(< <=) => true, %w(< ~>) => lambda{|s, o| s > o}, %w(>= >=) => true, %w(>= <=) => lambda{|s, o| s <= o}, %w(>= ~>) => lambda{|s, o| s < o.bump}, %w(<= <=) => true, %w(<= ~>) => lambda{|s, o| s >= o}, %w(~> ~>) => lambda{|s, o| s < o.bump && s.bump > o}, } def consistent_with?(other) sgreq, ogreq = to_gem_requirement, other.to_gem_requirement sreqs, oreqs = sgreq.requirements, ogreq.requirements sreqs.all? do |sreq| oreqs.all? do |oreq| compatible?(sreq, oreq) end end end def inconsistent_with?(other) !consistent_with?(other) end protected attr_accessor :backing private def initialize_normalize_args(args) args.map do |arg| arg = arg.backing if self.class === arg arg end end def compatible?(a, b) a, b = b, a unless COMPATS_TABLE.include?([a.first, b.first]) r = COMPATS_TABLE[[a.first, b.first]] r = r.call(a.last, b.last) if r.respond_to?(:call) r end end attr_accessor :name, :requirement, :source private :name=, :requirement=, :source= def initialize(name, requirement, source) assert_name_valid! name self.name = name self.requirement = Requirement.new(requirement) self.source = source @manifests = nil end def manifests @manifests ||= cache_manifests! end def cache_manifests! source.manifests(name) end def satisfied_by?(manifest) manifest.satisfies?(self) end def to_s "#{name} (#{requirement}) <#{source}>" end def ==(other) !other.nil? && self.class == other.class && self.name == other.name && self.requirement == other.requirement && self.source == other.source end def consistent_with?(other) name != other.name || requirement.consistent_with?(other.requirement) end def inconsistent_with?(other) !consistent_with?(other) end private def assert_name_valid!(name) raise ArgumentError, "name (#{name.inspect}) must be sensible" unless name =~ /\A\S(?:.*\S)?\z/ end end end librarian-0.1.1/lib/librarian/version.rb0000644000004100000410000000005112210667660020231 0ustar www-datawww-datamodule Librarian VERSION = "0.1.1" end librarian-0.1.1/lib/librarian/mock/0000755000004100000410000000000012210667660017154 5ustar www-datawww-datalibrarian-0.1.1/lib/librarian/mock/version.rb0000644000004100000410000000007712210667660021172 0ustar www-datawww-datamodule Librarian module Mock VERSION = "0.1.2" end end librarian-0.1.1/lib/librarian/mock/source/0000755000004100000410000000000012210667660020454 5ustar www-datawww-datalibrarian-0.1.1/lib/librarian/mock/source/mock/0000755000004100000410000000000012210667660021405 5ustar www-datawww-datalibrarian-0.1.1/lib/librarian/mock/source/mock/registry.rb0000644000004100000410000000416112210667660023604 0ustar www-datawww-datamodule Librarian module Mock module Source class Mock class Registry module Dsl class Top def initialize(sources) @sources = sources end def source(name, &block) @sources[name] ||= {} Source.new(@sources[name]).instance_eval(&block) if block end end class Source def initialize(source) @source = source end def spec(name, version = nil, &block) @source[name] ||= [] unless version Spec.new(@source[name]).instance_eval(&block) if block else Spec.new(@source[name]).version(version, &block) end @source[name] = @source[name].sort_by{|a| Manifest::Version.new(a[:version])}.reverse end end class Spec def initialize(spec) @spec = spec end def version(name, &block) @spec << { :version => name, :dependencies => {} } Version.new(@spec.last[:dependencies]).instance_eval(&block) if block end end class Version def initialize(version) @version = version end def dependency(name, *requirement) @version[name] = requirement end end class << self def run!(sources, &block) Top.new(sources).instance_eval(&block) if block end end end def initialize clear! end def clear! self.sources = { } end def merge!(options = nil, &block) clear! if options && options[:clear] Dsl.run!(sources, &block) if block end def [](name) sources[name] ||= {} end private attr_accessor :sources end end end end end librarian-0.1.1/lib/librarian/mock/source/mock.rb0000644000004100000410000000313412210667660021733 0ustar www-datawww-datarequire 'librarian/manifest' require 'librarian/source/basic_api' require 'librarian/mock/source/mock/registry' module Librarian module Mock module Source class Mock include Librarian::Source::BasicApi lock_name 'MOCK' spec_options [] attr_accessor :environment private :environment= attr_reader :name def initialize(environment, name, options) self.environment = environment @name = name end def to_s name end def ==(other) other && self.class == other.class && self.name == other.name end def to_spec_args [name, {}] end def to_lock_options {:remote => name} end def registry environment.registry[name] end def manifest(name, version, dependencies) manifest = Manifest.new(self, name) manifest.version = version manifest.dependencies = dependencies manifest end def manifests(name) if d = registry[name] d.map{|v| manifest(name, v[:version], v[:dependencies])} else nil end end def install!(manifest) end def to_s name end def fetch_version(name, extra) extra end def fetch_dependencies(name, version, extra) d = registry[name] m = d.find{|v| v[:version] == version.to_s} m[:dependencies] end end end end end librarian-0.1.1/lib/librarian/mock/cli.rb0000644000004100000410000000042412210667660020250 0ustar www-datawww-datarequire 'librarian/cli' require 'librarian/mock' module Librarian module Mock class Cli < Librarian::Cli module Particularity def root_module Mock end end include Particularity extend Particularity end end end librarian-0.1.1/lib/librarian/mock/environment.rb0000644000004100000410000000060512210667660022046 0ustar www-datawww-datarequire "librarian/environment" require "librarian/mock/dsl" require "librarian/mock/version" module Librarian module Mock class Environment < Environment def install_path nil end def registry(options = nil, &block) @registry ||= Source::Mock::Registry.new @registry.merge!(options, &block) @registry end end end end librarian-0.1.1/lib/librarian/mock/source.rb0000644000004100000410000000004512210667660021000 0ustar www-datawww-datarequire 'librarian/mock/source/mock' librarian-0.1.1/lib/librarian/mock/dsl.rb0000644000004100000410000000035212210667660020263 0ustar www-datawww-datarequire 'librarian/dsl' require 'librarian/mock/source' module Librarian module Mock class Dsl < Librarian::Dsl dependency :dep source :src => Source::Mock shortcut :a, :src => 'source-a' end end end librarian-0.1.1/lib/librarian/mock/extension.rb0000644000004100000410000000016512210667660021517 0ustar www-datawww-datarequire 'librarian/mock/environment' module Librarian module Mock extend self extend Librarian end end librarian-0.1.1/lib/librarian/source/0000755000004100000410000000000012210667660017523 5ustar www-datawww-datalibrarian-0.1.1/lib/librarian/source/basic_api.rb0000644000004100000410000000217212210667660021764 0ustar www-datawww-datamodule Librarian module Source module BasicApi def self.included(base) base.extend ClassMethods class << base def lock_name(name) def_sclass_prop(:lock_name, name) end def spec_options(keys) def_sclass_prop(:spec_options, keys) end private def def_sclass_prop(name, arg) sclass = class << self ; self ; end sclass.module_exec do remove_method(name) define_method(name) { arg } end end end end module ClassMethods def from_lock_options(environment, options) new(environment, options[:remote], options.reject{|k, v| k == :remote}) end def from_spec_args(environment, param, options) recognized_options = spec_options unrecognized_options = options.keys - recognized_options unrecognized_options.empty? or raise Error, "unrecognized options: #{unrecognized_options.join(", ")}" new(environment, param, options) end end end end end librarian-0.1.1/lib/librarian/source/git.rb0000644000004100000410000000651412210667660020641 0ustar www-datawww-datarequire 'fileutils' require 'pathname' require 'digest' require 'librarian/error' require 'librarian/source/basic_api' require 'librarian/source/git/repository' require 'librarian/source/local' module Librarian module Source class Git include BasicApi include Local lock_name 'GIT' spec_options [:ref, :path] DEFAULTS = { :ref => 'master' } attr_accessor :environment private :environment= attr_accessor :uri, :ref, :sha, :path private :uri=, :ref=, :sha=, :path= def initialize(environment, uri, options) self.environment = environment self.uri = uri self.ref = options[:ref] || DEFAULTS[:ref] self.sha = options[:sha] self.path = options[:path] @repository = nil @repository_cache_path = nil ref.kind_of?(String) or raise TypeError, "ref must be a String" end def to_s path ? "#{uri}##{ref}(#{path})" : "#{uri}##{ref}" end def ==(other) other && self.class == other.class && self.uri == other.uri && self.ref == other.ref && self.path == other.path && (self.sha.nil? || other.sha.nil? || self.sha == other.sha) end def to_spec_args options = {} options.merge!(:ref => ref) if ref != DEFAULTS[:ref] options.merge!(:path => path) if path [uri, options] end def to_lock_options options = {:remote => uri, :ref => ref, :sha => sha} options.merge!(:path => path) if path options end def pinned? !!sha end def unpin! @sha = nil end def cache! repository_cached? and return or repository_cached! unless repository.git? repository.path.rmtree if repository.path.exist? repository.path.mkpath repository.clone!(uri) raise Error, "failed to clone #{uri}" unless repository.git? end repository.reset_hard! repository.clean! unless repository.checked_out?(sha) remote = repository.default_remote repository.fetch!(remote) repository.fetch!(remote, :tags => true) self.sha = repository.hash_from(remote, ref) unless sha repository.checkout!(sha) unless repository.checked_out?(sha) raise Error, "failed to checkout #{sha}" unless repository.checked_out?(sha) end end private attr_accessor :repository_cached alias repository_cached? repository_cached def repository_cached! self.repository_cached = true end def repository_cache_path @repository_cache_path ||= begin environment.cache_path.join("source/git/#{cache_key}") end end def repository @repository ||= begin Repository.new(environment, repository_cache_path) end end def filesystem_path @filesystem_path ||= path ? repository.path.join(path) : repository.path end def cache_key @cache_key ||= begin uri_part = uri path_part = "/#{path}" if path ref_part = "##{ref}" key_source = [uri_part, path_part, ref_part].join Digest::MD5.hexdigest(key_source)[0..15] end end end end end librarian-0.1.1/lib/librarian/source/git/0000755000004100000410000000000012210667660020306 5ustar www-datawww-datalibrarian-0.1.1/lib/librarian/source/git/repository.rb0000644000004100000410000001012312210667660023047 0ustar www-datawww-datarequire "librarian/posix" module Librarian module Source class Git class Repository class << self def clone!(environment, path, repository_url) path = Pathname.new(path) path.mkpath git = new(environment, path) git.clone!(repository_url) git end def bin @bin ||= Posix.which!("git") end def git_version command = %W[#{bin} version --silent] Posix.run!(command).strip =~ /([.\d]+)$/ && $1 end end attr_accessor :environment, :path private :environment=, :path= def initialize(environment, path) self.environment = environment self.path = Pathname.new(path) end def git? path.join('.git').exist? end def default_remote "origin" end def clone!(repository_url) command = %W(clone #{repository_url} . --quiet) run!(command, :chdir => true) end def checkout!(reference, options ={ }) command = %W(checkout #{reference} --quiet) command << "--force" if options[:force] run!(command, :chdir => true) end def fetch!(remote, options = { }) command = %W(fetch #{remote} --quiet) command << "--tags" if options[:tags] run!(command, :chdir => true) end def reset_hard! command = %W(reset --hard --quiet) run!(command, :chdir => true) end def clean! command = %w(clean -x -d --force --force) run!(command, :chdir => true) end def checked_out?(sha) current_commit_hash == sha end def remote_names command = %W(remote) run!(command, :chdir => true).strip.lines.map(&:strip) end def remote_branch_names remotes = remote_names.sort_by(&:length).reverse command = %W(branch -r --no-color) names = run!(command, :chdir => true).strip.lines.map(&:strip).to_a names.each{|n| n.gsub!(/\s*->.*$/, "")} names.reject!{|n| n =~ /\/HEAD$/} Hash[remotes.map do |r| matching_names = names.select{|n| n.start_with?("#{r}/")} matching_names.each{|n| names.delete(n)} matching_names.each{|n| n.slice!(0, r.size + 1)} [r, matching_names] end] end def hash_from(remote, reference) branch_names = remote_branch_names[remote] if branch_names.include?(reference) reference = "#{remote}/#{reference}" end command = %W(rev-list #{reference} -1) run!(command, :chdir => true).strip end def current_commit_hash command = %W(rev-parse HEAD --quiet) run!(command, :chdir => true).strip! end private def bin self.class.bin end def run!(args, options = { }) chdir = options.delete(:chdir) chdir = path.to_s if chdir == true silent = options.delete(:silent) pwd = chdir || Dir.pwd git_dir = File.join(path, ".git") if path env = {"GIT_DIR" => git_dir} command = [bin] command.concat(args) logging_command(command, :silent => silent, :pwd => pwd) do Posix.run!(command, :chdir => chdir, :env => env) end end def logging_command(command, options) silent = options.delete(:silent) pwd = Dir.pwd out = yield unless silent if out.size > 0 out.lines.each do |line| debug { " --> #{line}" } end else debug { " --- No output" } end end out end def debug(*args, &block) environment.logger.debug(*args, &block) end def relative_path_to(path) environment.logger.relative_path_to(path) end end end end end librarian-0.1.1/lib/librarian/source/path.rb0000644000004100000410000000165412210667660021012 0ustar www-datawww-datarequire 'librarian/source/basic_api' require 'librarian/source/local' module Librarian module Source class Path include BasicApi include Local lock_name 'PATH' spec_options [] attr_accessor :environment private :environment= attr_reader :path def initialize(environment, path, options) self.environment = environment @path = path end def to_s path.to_s end def ==(other) other && self.class == other.class && self.path == other.path end def to_spec_args [path.to_s, {}] end def to_lock_options {:remote => path} end def pinned? false end def unpin! end def cache! end def filesystem_path @filesystem_path ||= Pathname.new(path).expand_path(environment.project_path) end end end end librarian-0.1.1/lib/librarian/source/local.rb0000644000004100000410000000233212210667660021142 0ustar www-datawww-datarequire 'librarian/support/abstract_method' module Librarian module Source # Requires that the including source class have methods: # #path # #environment module Local include Support::AbstractMethod abstract_method :path, :fetch_version, :fetch_dependencies def manifests(name) manifest = Manifest.new(self, name) [manifest].compact end def manifest_search_paths(name) @manifest_search_paths ||= { } @manifest_search_paths[name] ||= begin cache! paths = [filesystem_path, filesystem_path.join(name)] paths.select{|s| s.exist?} end end def found_path(name) @_found_paths ||= { } @_found_paths[name] ||= begin paths = manifest_search_paths(name) paths.find{|p| manifest?(name, p)} end end private abstract_method :manifest? # (name, path) -> boolean def info(*args, &block) environment.logger.info(*args, &block) end def debug(*args, &block) environment.logger.debug(*args, &block) end def relative_path_to(path) environment.logger.relative_path_to(path) end end end end librarian-0.1.1/lib/librarian/spec_change_set.rb0000644000004100000410000001374112210667660021670 0ustar www-datawww-datarequire 'librarian/helpers' require 'librarian/manifest_set' require 'librarian/resolution' require 'librarian/spec' module Librarian class SpecChangeSet attr_accessor :environment private :environment= attr_reader :spec, :lock def initialize(environment, spec, lock) self.environment = environment raise TypeError, "can't convert #{spec.class} into #{Spec}" unless Spec === spec raise TypeError, "can't convert #{lock.class} into #{Resolution}" unless Resolution === lock @spec, @lock = spec, lock end def same? @same ||= spec.dependencies.sort_by{|d| d.name} == lock.dependencies.sort_by{|d| d.name} end def changed? !same? end def spec_dependencies @spec_dependencies ||= spec.dependencies end def spec_dependency_names @spec_dependency_names ||= Set.new(spec_dependencies.map{|d| d.name}) end def spec_dependency_index @spec_dependency_index ||= Hash[spec_dependencies.map{|d| [d.name, d]}] end def lock_dependencies @lock_dependencies ||= lock.dependencies end def lock_dependency_names @lock_dependency_names ||= Set.new(lock_dependencies.map{|d| d.name}) end def lock_dependency_index @lock_dependency_index ||= Hash[lock_dependencies.map{|d| [d.name, d]}] end def lock_manifests @lock_manifests ||= lock.manifests end def lock_manifests_index @lock_manifests_index ||= ManifestSet.new(lock_manifests).to_hash end def removed_dependency_names @removed_dependency_names ||= lock_dependency_names - spec_dependency_names end # A dependency which is deleted from the specfile will, in the general case, # be removed conservatively. This means it might not actually be removed. # But if the dependency originally declared a source which is now non- # default, it must be removed, even if another dependency has a transitive # dependency on the one that was removed (which is the scenario in which # a conservative removal would not remove it). In this case, we must also # remove it explicitly so that it can be re-resolved from the default # source. def explicit_removed_dependency_names @explicit_removed_dependency_names ||= removed_dependency_names.reject do |name| lock_manifest = lock_manifests_index[name] spec.sources.include?(lock_manifest.source) end.to_set end def added_dependency_names @added_dependency_names ||= spec_dependency_names - lock_dependency_names end def nonmatching_added_dependency_names @nonmatching_added_dependency_names ||= added_dependency_names.reject do |name| spec_dependency = spec_dependency_index[name] lock_manifest = lock_manifests_index[name] if lock_manifest matching = true matching &&= spec_dependency.satisfied_by?(lock_manifest) matching &&= spec_dependency.source == lock_manifest.source matching else false end end.to_set end def common_dependency_names @common_dependency_names ||= lock_dependency_names & spec_dependency_names end def changed_dependency_names @changed_dependency_names ||= common_dependency_names.reject do |name| spec_dependency = spec_dependency_index[name] lock_dependency = lock_dependency_index[name] lock_manifest = lock_manifests_index[name] same = true same &&= spec_dependency.satisfied_by?(lock_manifest) same &&= spec_dependency.source == lock_dependency.source same end.to_set end def deep_keep_manifest_names @deep_keep_manifest_names ||= begin lock_dependency_names - ( removed_dependency_names + changed_dependency_names + nonmatching_added_dependency_names ) end end def shallow_strip_manifest_names @shallow_strip_manifest_names ||= begin explicit_removed_dependency_names + changed_dependency_names end end def inspect Helpers.strip_heredoc(<<-INSPECT) <##{self.class.name}: Removed: #{removed_dependency_names.to_a.join(", ")} ExplicitRemoved: #{explicit_removed_dependency_names.to_a.join(", ")} Added: #{added_dependency_names.to_a.join(", ")} NonMatchingAdded: #{nonmatching_added_dependency_names.to_a.join(", ")} Changed: #{changed_dependency_names.to_a.join(", ")} DeepKeep: #{deep_keep_manifest_names.to_a.join(", ")} ShallowStrip: #{shallow_strip_manifest_names.to_a.join(", ")} > INSPECT end # Returns an array of those manifests from the previous spec which should be kept, # based on inspecting the new spec against the locked resolution from the previous spec. def analyze @analyze ||= begin debug { "Analyzing spec and lock:" } if same? debug { " Same!" } return lock.manifests end debug { " Removed:" } ; removed_dependency_names.each { |name| debug { " #{name}" } } debug { " ExplicitRemoved:" } ; explicit_removed_dependency_names.each { |name| debug { " #{name}" } } debug { " Added:" } ; added_dependency_names.each { |name| debug { " #{name}" } } debug { " NonMatchingAdded:" } ; nonmatching_added_dependency_names.each { |name| debug { " #{name}" } } debug { " Changed:" } ; changed_dependency_names.each { |name| debug { " #{name}" } } debug { " DeepKeep:" } ; deep_keep_manifest_names.each { |name| debug { " #{name}" } } debug { " ShallowStrip:" } ; shallow_strip_manifest_names.each { |name| debug { " #{name}" } } manifests = ManifestSet.new(lock_manifests) manifests.deep_keep!(deep_keep_manifest_names) manifests.shallow_strip!(shallow_strip_manifest_names) manifests.to_a end end private def debug(*args, &block) environment.logger.debug(*args, &block) end end end librarian-0.1.1/lib/librarian/cli.rb0000644000004100000410000001476512210667660017334 0ustar www-datawww-datarequire 'thor' require 'thor/actions' require 'librarian' require 'librarian/error' require 'librarian/action' require "librarian/ui" module Librarian class Cli < Thor autoload :ManifestPresenter, "librarian/cli/manifest_presenter" include Thor::Actions module Particularity def root_module nil end end extend Particularity class << self def bin! status = with_environment { returning_status { start } } exit status end def returning_status yield 0 rescue Librarian::Error => e environment.ui.error e.message environment.ui.debug e.backtrace.join("\n") e.respond_to?(:status_code) && e.status_code || 1 rescue Interrupt => e environment.ui.error "\nQuitting..." 1 end attr_accessor :environment def with_environment environment = root_module.environment_class.new self.environment, orig_environment = environment, self.environment yield(environment) ensure self.environment = orig_environment end end def initialize(*) super the_shell = (options["no-color"] ? Thor::Shell::Basic.new : shell) environment.ui = UI::Shell.new(the_shell) environment.ui.be_quiet! if options["quiet"] environment.ui.debug! if options["verbose"] environment.ui.debug_line_numbers! if options["verbose"] && options["line-numbers"] write_debug_header end desc "version", "Displays the version." def version say "librarian-#{environment.version}" say "librarian-#{environment.adapter_name}-#{environment.adapter_version}" end desc "config", "Show or edit the config." option "verbose", :type => :boolean, :default => false option "line-numbers", :type => :boolean, :default => false option "global", :type => :boolean, :default => false option "local", :type => :boolean, :default => false option "delete", :type => :boolean, :default => false def config(key = nil, value = nil) if key raise Error, "cannot set both value and delete" if value && options["delete"] if options["delete"] scope = config_scope(true) environment.config_db[key, scope] = nil elsif value scope = config_scope(true) environment.config_db[key, scope] = value else scope = config_scope(false) if value = environment.config_db[key, scope] prefix = scope ? "#{key} (#{scope})" : key say "#{prefix}: #{value}" end end else environment.config_db.keys.each do |key| say "#{key}: #{environment.config_db[key]}" end end end desc "clean", "Cleans out the cache and install paths." option "verbose", :type => :boolean, :default => false option "line-numbers", :type => :boolean, :default => false def clean ensure! clean! end desc "update", "Updates and installs the dependencies you specify." option "verbose", :type => :boolean, :default => false option "line-numbers", :type => :boolean, :default => false def update(*names) ensure! if names.empty? resolve!(:force => true) else update!(:names => names) end install! end desc "outdated", "Lists outdated dependencies." option "verbose", :type => :boolean, :default => false option "line-numbers", :type => :boolean, :default => false def outdated ensure! resolution = environment.lock manifests = resolution.manifests.sort_by(&:name) manifests.select(&:outdated?).each do |manifest| say "#{manifest.name} (#{manifest.version} -> #{manifest.latest.version})" end end desc "show", "Shows dependencies" option "verbose", :type => :boolean, :default => false option "line-numbers", :type => :boolean, :default => false option "detailed", :type => :boolean def show(*names) ensure! if environment.lockfile_path.file? manifest_presenter.present(names, :detailed => options["detailed"]) else raise Error, "Be sure to install first!" end end desc "init", "Initializes the current directory." def init puts "Nothing to do." end private def environment self.class.environment end def ensure!(options = { }) Action::Ensure.new(environment, options).run end def clean!(options = { }) Action::Clean.new(environment, options).run end def install!(options = { }) Action::Install.new(environment, options).run end def resolve!(options = { }) Action::Resolve.new(environment, options).run end def update!(options = { }) Action::Update.new(environment, options).run end def manifest_presenter ManifestPresenter.new(self, environment.lock.manifests) end def write_debug_header debug { "Ruby Version: #{RUBY_VERSION}" } debug { "Ruby Platform: #{RUBY_PLATFORM}" } debug { "Rubinius Version: #{Rubinius::VERSION}" } if defined?(Rubinius) debug { "JRuby Version: #{JRUBY_VERSION}" } if defined?(JRUBY_VERSION) debug { "Rubygems Version: #{Gem::VERSION}" } debug { "Librarian Version: #{environment.version}" } debug { "Librarian Adapter: #{environment.adapter_name}"} debug { "Librarian Adapter Version: #{environment.adapter_version}" } debug { "Project: #{environment.project_path}" } debug { "Specfile: #{relative_path_to(environment.specfile_path)}" } debug { "Lockfile: #{relative_path_to(environment.lockfile_path)}" } debug { "Git: #{Source::Git::Repository.bin}" } debug { "Git Version: #{Source::Git::Repository.git_version}" } debug { "Git Environment Variables:" } git_env = ENV.to_a.select{|(k, v)| k =~ /\AGIT/}.sort_by{|(k, v)| k} if git_env.empty? debug { " (empty)" } else git_env.each do |(k, v)| debug { " #{k}=#{v}"} end end end def debug(*args, &block) environment.logger.debug(*args, &block) end def relative_path_to(path) environment.logger.relative_path_to(path) end def config_scope(exclusive) g, l = "global", "local" if exclusive options[g] ^ options[l] or raise Error, "must set either #{g} or #{l}" else options[g] && options[l] and raise Error, "cannot set both #{g} and #{l}" end options[g] ? :global : options[l] ? :local : nil end end end librarian-0.1.1/lib/librarian/cli/0000755000004100000410000000000012210667660016772 5ustar www-datawww-datalibrarian-0.1.1/lib/librarian/cli/manifest_presenter.rb0000644000004100000410000000436712210667660023226 0ustar www-datawww-datamodule Librarian class Cli class ManifestPresenter attr_accessor :cli, :manifests private :cli=, :manifests= def initialize(cli, manifests) self.cli = cli or raise ArgumentError, "cli required" self.manifests = manifests or raise ArgumentError, "manifests required" self.manifests_index = Hash[manifests.map{|m| [m.name, m]}] self.scope_level = 0 end def present(names = [], options = { }) full = options[:detailed] full = !names.empty? if full.nil? names = manifests.map(&:name).sort if names.empty? assert_no_manifests_missing!(names) present_each(names, :detailed => full) end def present_one(manifest, options = { }) full = options[:detailed] say "#{manifest.name} (#{manifest.version})" do full or next present_one_source(manifest) present_one_dependencies(manifest) end end private def present_each(names, options) names.each do |name| manifest = manifest(name) present_one(manifest, options) end end def present_one_source(manifest) say "source: #{manifest.source}" end def present_one_dependencies(manifest) manifest.dependencies.empty? and return say "dependencies:" do deps = manifest.dependencies.sort_by(&:name) deps.each do |dependency| say "#{dependency.name} (#{dependency.requirement})" end end end attr_accessor :scope_level, :manifests_index def manifest(name) manifests_index[name] end def say(string) cli.say " " * scope_level << string if block_given? scope do yield end end end def scope original_scope_level = scope_level self.scope_level = scope_level + 1 yield ensure self.scope_level = original_scope_level end def assert_no_manifests_missing!(names) missing_names = names.reject{|name| manifest(name)} unless missing_names.empty? raise Error, "not found: #{missing_names.map(&:inspect).join(', ')}" end end end end end librarian-0.1.1/lib/librarian/mock.rb0000644000004100000410000000004312210667660017476 0ustar www-datawww-datarequire 'librarian/mock/extension' librarian-0.1.1/lib/librarian/dsl/0000755000004100000410000000000012210667660017005 5ustar www-datawww-datalibrarian-0.1.1/lib/librarian/dsl/receiver.rb0000644000004100000410000000216212210667660021137 0ustar www-datawww-datarequire "pathname" module Librarian class Dsl class Receiver def initialize(target) singleton_class = class << self; self end singleton_class.class_eval do define_method(target.dependency_name) do |*args, &block| target.dependency(*args, &block) end define_method(:source) do |*args, &block| target.source(*args, &block) end target.source_types.each do |source_type| name = source_type[0] define_method(name) do |*args, &block| target.source(name, *args, &block) end end end end def run(specfile = nil) specfile = Proc.new if block_given? case specfile when Pathname instance_eval(File.read(specfile), specfile.to_s, 1) when String instance_eval(specfile) when Proc instance_eval(&specfile) else raise ArgumentError, "specfile must be a #{Pathname}, #{String}, or #{Proc} if no block is given (it was #{specfile.inspect})" end end end end end librarian-0.1.1/lib/librarian/dsl/target.rb0000644000004100000410000001161712210667660020626 0ustar www-datawww-datarequire 'librarian/spec' module Librarian class Dsl class Target class SourceShortcutDefinitionReceiver def initialize(target) singleton_class = class << self; self end singleton_class.class_eval do define_method(:source) do |options| target.source_from_options(options) end target.source_types.each do |source_type| name = source_type[0] define_method(name) do |*args| args.push({}) unless Hash === args.last target.source_from_params(name, *args) end end end end end SCOPABLES = [:source, :sources] attr_accessor :dsl private :dsl= attr_reader :dependency_name, :dependency_type attr_reader :source_types, :source_types_map, :source_types_reverse_map, :source_type_names, :source_shortcuts attr_reader :dependencies, :source_cache, *SCOPABLES def initialize(dsl) self.dsl = dsl @dependency_name = dsl.dependency_name @dependency_type = dsl.dependency_type @source_types = dsl.source_types @source_types_map = Hash[source_types] @source_types_reverse_map = Hash[source_types.map{|pair| a, b = pair ; [b, a]}] @source_type_names = source_types.map{|t| t[0]} @source_cache = {} @source_shortcuts = {} @dependencies = [] SCOPABLES.each do |scopable| instance_variable_set(:"@#{scopable}", []) end dsl.source_shortcuts.each do |name, param| define_source_shortcut(name, param) end end def to_spec Spec.new(@sources, @dependencies) end def dependency(name, *args) options = args.last.is_a?(Hash) ? args.pop : {} source = source_from_options(options) || @source dep = dependency_type.new(name, args, source) @dependencies << dep end def source(name, param = nil, options = nil, &block) if !(Hash === name) && [Array, Hash, Proc].any?{|c| c === param} && !options && !block define_source_shortcut(name, param) elsif !(Hash === name) && !param && !options source = source_shortcuts[name] scope_or_directive(block) do @source = source @sources = @sources.dup << source end else name, param, options = *normalize_source_options(name, param, options || {}) source = source_from_params(name, param, options) scope_or_directive(block) do @source = source @sources = @sources.dup << source end end end def precache_sources(sources) sources.each do |source| key = [source_types_reverse_map[source.class], *source.to_spec_args] source_cache[key] = source end end def scope currents = { } SCOPABLES.each do |scopable| currents[scopable] = instance_variable_get(:"@#{scopable}").dup end yield ensure SCOPABLES.reverse.each do |scopable| instance_variable_set(:"@#{scopable}", currents[scopable]) end end def scope_or_directive(scoped_block = nil) unless scoped_block yield else scope do yield scoped_block.call end end end def normalize_source_options(name, param, options) if name.is_a?(Hash) extract_source_parts(name) else [name, param, options] end end def extract_source_parts(options) if name = source_type_names.find{|name| options.key?(name)} options = options.dup param = options.delete(name) [name, param, options] else nil end end def source_from_options(options) if options[:source] source_shortcuts[options[:source]] elsif source_parts = extract_source_parts(options) source_from_params(*source_parts) else nil end end def source_from_params(name, param, options) source_cache[[name, param, options]] ||= begin type = source_types_map[name] type.from_spec_args(environment, param, options) end end def source_from_source_shortcut_definition(definition) case definition when Array source_from_params(*definition) when Hash source_from_options(definition) when Proc receiver = SourceShortcutDefinitionReceiver.new(self) receiver.instance_eval(&definition) end end def define_source_shortcut(name, definition) source = source_from_source_shortcut_definition(definition) source_shortcuts[name] = source end def environment dsl.environment end end end end librarian-0.1.1/lib/librarian/resolution.rb0000644000004100000410000000267712210667660020767 0ustar www-datawww-datamodule Librarian # # Represents the output of the resolution process. Captures the declared # dependencies plus the full set of resolved manifests. The sources are # already known by the dependencies and by the resolved manifests, so they do # not need to be captured explicitly. # # This representation may be produced by the resolver, may be serialized into # a lockfile, and may be deserialized from a lockfile. It is expected that the # lockfile is a direct representation in text of this representation, so that # the serialization-deserialization process is just the identity function. # class Resolution attr_accessor :dependencies, :manifests, :manifests_index private :dependencies=, :manifests=, :manifests_index= def initialize(dependencies, manifests) self.dependencies = dependencies self.manifests = manifests self.manifests_index = build_manifests_index(manifests) end def correct? manifests && manifests_consistent_with_dependencies? && manifests_internally_consistent? end def sources manifests.map(&:source).uniq end private def build_manifests_index(manifests) Hash[manifests.map{|m| [m.name, m]}] if manifests end def manifests_consistent_with_dependencies? ManifestSet.new(manifests).in_compliance_with?(dependencies) end def manifests_internally_consistent? ManifestSet.new(manifests).consistent? end end end librarian-0.1.1/lib/librarian/linter/0000755000004100000410000000000012210667660017520 5ustar www-datawww-datalibrarian-0.1.1/lib/librarian/linter/source_linter.rb0000644000004100000410000000214412210667660022723 0ustar www-datawww-datamodule Librarian module Linter class SourceLinter class << self def lint!(klass) new(klass).lint! end end attr_accessor :klass private :klass= def initialize(klass) self.klass = klass end def lint! lint_class_responds_to! *[ :lock_name, :from_spec_args, :from_lock_options, ] lint_instance_responds_to! *[ :to_spec_args, :to_lock_options, :manifests, :fetch_version, :fetch_dependencies, :pinned?, :unpin!, :install!, ] end private def lint_class_responds_to!(*names) missing = names.reject{|name| klass.respond_to?(name)} return if missing.empty? raise "class must respond to #{missing.join(', ')}" end def lint_instance_responds_to!(*names) missing = names - klass.public_instance_methods.map(&:to_sym) return if missing.empty? raise "instance must respond to #{missing.join(', ')}" end end end end librarian-0.1.1/lib/librarian/action.rb0000644000004100000410000000025312210667660020025 0ustar www-datawww-datarequire "librarian/action/clean" require "librarian/action/ensure" require "librarian/action/install" require "librarian/action/resolve" require "librarian/action/update" librarian-0.1.1/lib/librarian/manifest.rb0000644000004100000410000000535612210667660020367 0ustar www-datawww-datarequire 'rubygems' module Librarian class Manifest class Version include Comparable def initialize(*args) args = initialize_normalize_args(args) self.backing = Gem::Version.new(*args) end def to_gem_version backing end def <=>(other) to_gem_version <=> other.to_gem_version end def to_s to_gem_version.to_s end private def initialize_normalize_args(args) args.map do |arg| arg = [arg] if self.class === arg arg end end attr_accessor :backing end attr_accessor :source, :name, :extra private :source=, :name=, :extra= def initialize(source, name, extra = nil) assert_name_valid! name self.source = source self.name = name self.extra = extra end def to_s "#{name}/#{version} <#{source}>" end def version defined_version || fetched_version end def version=(version) self.defined_version = _normalize_version(version) end def version? return unless defined_version defined_version == fetched_version end def latest @latest ||= source.manifests(name).first end def outdated? latest.version > version end def dependencies defined_dependencies || fetched_dependencies end def dependencies=(dependencies) self.defined_dependencies = _normalize_dependencies(dependencies) end def dependencies? return unless defined_dependencies defined_dependencies.zip(fetched_dependencies).all? do |(a, b)| a.name == b.name && a.requirement == b.requirement end end def satisfies?(dependency) dependency.requirement.satisfied_by?(version) end def install! source.install!(self) end private attr_accessor :defined_version, :defined_dependencies def environment source.environment end def fetched_version @fetched_version ||= _normalize_version(fetch_version!) end def fetched_dependencies @fetched_dependencies ||= _normalize_dependencies(fetch_dependencies!) end def fetch_version! source.fetch_version(name, extra) end def fetch_dependencies! source.fetch_dependencies(name, version, extra) end def _normalize_version(version) Version.new(version) end def _normalize_dependencies(dependencies) if Hash === dependencies dependencies = dependencies.map{|k, v| Dependency.new(k, v, nil)} end dependencies.sort_by(&:name) end def assert_name_valid!(name) raise ArgumentError, "name (#{name.inspect}) must be sensible" unless name =~ /\A\S(?:.*\S)?\z/ end end end librarian-0.1.1/lib/librarian/ui.rb0000644000004100000410000000217312210667660017170 0ustar www-datawww-datarequire 'rubygems/user_interaction' module Librarian class UI def warn(message = nil) end def debug(message = nil) end def error(message = nil) end def info(message = nil) end def confirm(message = nil) end class Shell < UI attr_writer :shell attr_reader :debug_line_numbers def initialize(shell) @shell = shell @quiet = false @debug = ENV['DEBUG'] @debug_line_numbers = false end def debug(message = nil) @shell.say(message || yield) if @debug && !@quiet end def info(message = nil) @shell.say(message || yield) if !@quiet end def confirm(message = nil) @shell.say(message || yield, :green) if !@quiet end def warn(message = nil) @shell.say(message || yield, :yellow) end def error(message = nil) @shell.say(message || yield, :red) end def be_quiet! @quiet = true end def debug! @debug = true end def debug_line_numbers! @debug_line_numbers = true end end end endlibrarian-0.1.1/lib/librarian/environment.rb0000644000004100000410000001047312210667660021121 0ustar www-datawww-datarequire "pathname" require 'net/http' require "uri" require "etc" require "librarian/support/abstract_method" require "librarian/error" require "librarian/config" require "librarian/lockfile" require "librarian/logger" require "librarian/specfile" require "librarian/resolver" require "librarian/dsl" require "librarian/source" require "librarian/version" module Librarian class Environment include Support::AbstractMethod attr_accessor :ui abstract_method :specfile_name, :dsl_class, :install_path def initialize(options = { }) @pwd = options.fetch(:pwd) { Dir.pwd } @env = options.fetch(:env) { ENV.to_hash } @home = options.fetch(:home) { default_home } @project_path = options[:project_path] end def logger @logger ||= Logger.new(self) end def config_db @config_db ||= begin Config::Database.new(adapter_name, :pwd => @pwd, :env => @env, :home => @home, :project_path => @project_path, :specfile_name => default_specfile_name ) end end def default_specfile_name @default_specfile_name ||= begin capped = adapter_name.capitalize "#{capped}file" end end def project_path config_db.project_path end def specfile_name config_db.specfile_name end def specfile_path config_db.specfile_path end def specfile Specfile.new(self, specfile_path) end def adapter_module implementation? or return self.class.name.split("::")[0 ... -1].inject(Object, &:const_get) end def adapter_name implementation? or return Helpers.camel_cased_to_dasherized(self.class.name.split("::")[-2]) end def adapter_version implementation? or return adapter_module::VERSION end def lockfile_name config_db.lockfile_name end def lockfile_path config_db.lockfile_path end def lockfile Lockfile.new(self, lockfile_path) end def ephemeral_lockfile Lockfile.new(self, nil) end def resolver Resolver.new(self) end def tmp_path part = config_db["tmp"] || "tmp" project_path.join(part) end def cache_path tmp_path.join("librarian/cache") end def scratch_path tmp_path.join("librarian/scratch") end def project_relative_path_to(path) Pathname.new(path).relative_path_from(project_path) end def spec specfile.read end def lock lockfile.read end def dsl(*args, &block) dsl_class.run(self, *args, &block) end def dsl_class adapter_module::Dsl end def version VERSION end def config_keys %[ ] end # The HTTP proxy specified in the environment variables: # * HTTP_PROXY # * HTTP_PROXY_USER # * HTTP_PROXY_PASS # Adapted from: # https://github.com/rubygems/rubygems/blob/v1.8.24/lib/rubygems/remote_fetcher.rb#L276-293 def http_proxy_uri @http_proxy_uri ||= begin keys = %w( HTTP_PROXY HTTP_PROXY_USER HTTP_PROXY_PASS ) env = Hash[ENV. map{|k, v| [k.upcase, v]}. select{|k, v| keys.include?(k)}. reject{|k, v| v.nil? || v.empty?}] uri = env["HTTP_PROXY"] or return uri = "http://#{uri}" unless uri =~ /^(https?|ftp|file):/ uri = URI.parse(uri) uri.user ||= env["HTTP_PROXY_USER"] uri.password ||= env["HTTP_PROXY_PASS"] uri end end def net_http_class(host) no_proxy?(host) ? Net::HTTP : net_http_default_class end private def environment self end def implementation? self.class != ::Librarian::Environment end def default_home File.expand_path(ENV["HOME"] || Etc.getpwnam(Etc.getlogin).dir) end def no_proxy_list @no_proxy_list ||= begin list = ENV['NO_PROXY'] || ENV['no_proxy'] || "" list.split(/\s*,\s*/) + %w(localhost 127.0.0.1) end end def no_proxy?(host) no_proxy_list.any? do |host_addr| host.end_with?(host_addr) end end def net_http_default_class @net_http_default_class ||= begin p = http_proxy_uri p ? Net::HTTP::Proxy(p.host, p.port, p.user, p.password) : Net::HTTP end end end end librarian-0.1.1/lib/librarian/manifest_set.rb0000644000004100000410000000640512210667660021236 0ustar www-datawww-datarequire 'set' require 'tsort' module Librarian class ManifestSet class GraphHash < Hash include TSort alias tsort_each_node each_key def tsort_each_child(node, &block) self[node].each(&block) end end class << self def shallow_strip(manifests, names) new(manifests).shallow_strip!(names).send(method_for(manifests)) end def deep_strip(manifests, names) new(manifests).deep_strip!(names).send(method_for(manifests)) end def shallow_keep(manifests, names) new(manifests).shallow_keep!(names).send(method_for(manifests)) end def deep_keep(manifests, names) new(manifests).deep_keep!(names).send(method_for(manifests)) end def sort(manifests) manifests = Hash[manifests.map{|m| [m.name, m]}] if Array === manifests manifest_pairs = GraphHash[manifests.map{|k, m| [k, m.dependencies.map{|d| d.name}]}] manifest_names = manifest_pairs.tsort manifest_names.map{|n| manifests[n]} end private def method_for(manifests) case manifests when Hash :to_hash when Array :to_a end end end def initialize(manifests) self.index = Hash === manifests ? manifests.dup : index_by(manifests, &:name) end def to_a index.values end def to_hash index.dup end def dup self.class.new(index) end def shallow_strip(names) dup.shallow_strip!(names) end def shallow_strip!(names) assert_strings!(names) names.each do |name| index.delete(name) end self end def deep_strip(names) dup.deep_strip!(names) end def deep_strip!(names) strippables = dependencies_of(names) shallow_strip!(strippables) self end def shallow_keep(names) dup.shallow_keep!(names) end def shallow_keep!(names) assert_strings!(names) names = Set.new(names) unless Set === names index.reject! { |k, v| !names.include?(k) } self end def deep_keep(names) dup.conservative_strip!(names) end def deep_keep!(names) keepables = dependencies_of(names) shallow_keep!(keepables) self end def consistent? index.values.all? do |manifest| in_compliance_with?(manifest.dependencies) end end def in_compliance_with?(dependencies) dependencies.all? do |dependency| manifest = index[dependency.name] manifest && manifest.satisfies?(dependency) end end private attr_accessor :index def assert_strings!(names) non_strings = names.reject{|name| String === name} non_strings.empty? or raise TypeError, "names must all be strings" end # Straightforward breadth-first graph traversal algorithm. def dependencies_of(names) names = Array === names ? names.dup : names.to_a assert_strings!(names) deps = Set.new until names.empty? name = names.shift next if deps.include?(name) deps << name names.concat index[name].dependencies.map(&:name) end deps.to_a end def index_by(enum) Hash[enum.map{|obj| [yield(obj), obj]}] end end end librarian-0.1.1/lib/librarian/lockfile.rb0000644000004100000410000000077312210667660020347 0ustar www-datawww-datarequire 'librarian/lockfile/compiler' require 'librarian/lockfile/parser' module Librarian class Lockfile attr_accessor :environment private :environment= attr_reader :path def initialize(environment, path) self.environment = environment @path = path end def save(resolution) Compiler.new(environment).compile(resolution) end def load(string) Parser.new(environment).parse(string) end def read load(path.read) end end end librarian-0.1.1/lib/librarian/specfile.rb0000644000004100000410000000053212210667660020342 0ustar www-datawww-datarequire "pathname" module Librarian class Specfile attr_accessor :environment, :path private :environment=, :path= def initialize(environment, path) self.environment = environment self.path = Pathname(path) end def read(precache_sources = []) environment.dsl(path, precache_sources) end end end librarian-0.1.1/lib/librarian/logger.rb0000644000004100000410000000204512210667660020030 0ustar www-datawww-datamodule Librarian class Logger librarian_path = Pathname(__FILE__) librarian_path = librarian_path.dirname until librarian_path.join("lib").directory? LIBRARIAN_PATH = librarian_path attr_accessor :environment private :environment= def initialize(environment) self.environment = environment end def info(string = nil, &block) return unless ui ui.info(string || yield) end def debug(string = nil, &block) return unless ui if ui.respond_to?(:debug_line_numbers) && ui.debug_line_numbers loc = caller.find{|l| !(l =~ /in `debug'$/)} if loc =~ /^(.+):(\d+):in `(.+)'$/ loc = "#{Pathname.new($1).relative_path_from(LIBRARIAN_PATH)}:#{$2}:in `#{$3}'" end ui.debug { "[Librarian] #{string || yield} [#{loc}]" } else ui.debug { "[Librarian] #{string || yield}" } end end def relative_path_to(path) environment.project_relative_path_to(path) end private def ui environment.ui end end end librarian-0.1.1/lib/librarian/config.rb0000644000004100000410000000012112210667660020007 0ustar www-datawww-datarequire "librarian/config/database" module Librarian module Config end end librarian-0.1.1/lib/librarian/lockfile/0000755000004100000410000000000012210667660020013 5ustar www-datawww-datalibrarian-0.1.1/lib/librarian/lockfile/parser.rb0000644000004100000410000000747012210667660021644 0ustar www-datawww-datarequire 'librarian/manifest' require 'librarian/dependency' require 'librarian/manifest_set' module Librarian class Lockfile class Parser class ManifestPlaceholder attr_reader :source, :name, :version, :dependencies def initialize(source, name, version, dependencies) @source, @name, @version, @dependencies = source, name, version, dependencies end end attr_accessor :environment private :environment= def initialize(environment) self.environment = environment end def parse(string) lines = string.lines.map{|l| l.sub(/\s+\z/, '')}.reject(&:empty?) sources = extract_and_parse_sources(lines) manifests = compile(sources) manifests_index = Hash[manifests.map{|m| [m.name, m]}] raise StandardError, "Expected DEPENDENCIES topic!" unless lines.shift == "DEPENDENCIES" dependencies = extract_and_parse_dependencies(lines, manifests_index) Resolution.new(dependencies, manifests) end private def extract_and_parse_sources(lines) sources = [] while source_type_names.include?(lines.first) source = {} source_type_name = lines.shift source[:type] = source_type_names_map[source_type_name] options = {} while lines.first =~ /^ {2}([\w-]+):\s+(.+)$/ lines.shift options[$1.to_sym] = $2 end source[:options] = options lines.shift # specs manifests = {} while lines.first =~ /^ {4}([\w-]+) \((.*)\)$/ lines.shift name = $1 manifests[name] = {:version => $2, :dependencies => {}} while lines.first =~ /^ {6}([\w-]+) \((.*)\)$/ lines.shift manifests[name][:dependencies][$1] = $2.split(/,\s*/) end end source[:manifests] = manifests sources << source end sources end def extract_and_parse_dependencies(lines, manifests_index) dependencies = [] while lines.first =~ /^ {2}([\w-]+)(?: \((.*)\))?$/ lines.shift name, requirement = $1, $2.split(/,\s*/) dependencies << Dependency.new(name, requirement, manifests_index[name].source) end dependencies end def compile_placeholder_manifests(sources_ast) manifests = {} sources_ast.each do |source_ast| source_type = source_ast[:type] source = source_type.from_lock_options(environment, source_ast[:options]) source_ast[:manifests].each do |manifest_name, manifest_ast| manifests[manifest_name] = ManifestPlaceholder.new( source, manifest_name, manifest_ast[:version], manifest_ast[:dependencies].map{|k, v| Dependency.new(k, v, nil)} ) end end manifests end def compile(sources_ast) manifests = compile_placeholder_manifests(sources_ast) manifests = manifests.map do |name, manifest| dependencies = manifest.dependencies.map do |d| Dependency.new(d.name, d.requirement, manifests[d.name].source) end real = Manifest.new(manifest.source, manifest.name) real.version = manifest.version real.dependencies = manifest.dependencies real end ManifestSet.sort(manifests) end def dsl_class environment.dsl_class end def source_type_names_map @source_type_names_map ||= begin Hash[dsl_class.source_types.map{|t| [t[1].lock_name, t[1]]}] end end def source_type_names @source_type_names ||= begin dsl_class.source_types.map{|t| t[1].lock_name} end end end end end librarian-0.1.1/lib/librarian/lockfile/compiler.rb0000644000004100000410000000354712210667660022163 0ustar www-datawww-datamodule Librarian class Lockfile class Compiler attr_accessor :environment private :environment= def initialize(environment) self.environment = environment end def compile(resolution) out = StringIO.new save_sources(out, resolution.manifests) save_dependencies(out, resolution.dependencies) out.string end private def save_sources(out, manifests) dsl_class.source_types.map{|t| t[1]}.each do |type| type_manifests = manifests.select{|m| type === m.source} sources = type_manifests.map{|m| m.source}.uniq.sort_by{|s| s.to_s} sources.each do |source| source_manifests = type_manifests.select{|m| source == m.source} save_source(out, source, source_manifests) end end end def save_source(out, source, manifests) out.puts "#{source.class.lock_name}" options = source.to_lock_options remote = options.delete(:remote) out.puts " remote: #{remote}" options.to_a.sort_by{|a| a[0].to_s}.each do |o| out.puts " #{o[0]}: #{o[1]}" end out.puts " specs:" manifests.sort_by{|a| a.name}.each do |manifest| out.puts " #{manifest.name} (#{manifest.version})" manifest.dependencies.sort_by{|a| a.name}.each do |dependency| out.puts " #{dependency.name} (#{dependency.requirement})" end end out.puts "" end def save_dependencies(out, dependencies) out.puts "DEPENDENCIES" dependencies.sort_by{|a| a.name}.each do |d| res = "#{d.name}" res << " (#{d.requirement})" if d.requirement out.puts " #{res}" end out.puts "" end def dsl_class environment.dsl_class end end end end librarian-0.1.1/lib/librarian/source.rb0000644000004100000410000000007712210667660020054 0ustar www-datawww-datarequire 'librarian/source/git' require 'librarian/source/path' librarian-0.1.1/lib/librarian/dsl.rb0000644000004100000410000000504312210667660017334 0ustar www-datawww-datarequire 'librarian/dependency' require 'librarian/dsl/receiver' require 'librarian/dsl/target' module Librarian class Dsl class Error < Exception end attr_accessor :environment private :environment= class << self def run(environment, specfile = nil, precache_sources = [], &block) new(environment).run(specfile, precache_sources, &block) end private def dependency(name) dependency_name = name dependency_type = Dependency singleton_class = class << self; self end singleton_class.instance_eval do define_method(:dependency_name) { dependency_name } define_method(:dependency_type) { dependency_type } end end define_method(:source_types) { [] } def source(options) name = options.keys.first type = options[name] types = source_types types << [name, type] singleton_class = class << self; self end singleton_class.instance_eval do define_method(:source_types) { types } end end define_method(:source_shortcuts) { {} } def shortcut(name, options) instances = source_shortcuts instances[name] = options singleton_class = class << self; self end singleton_class.instance_eval do define_method(:source_shortcuts) { instances } end end def delegate_to_class(*names) names.each do |name| define_method(name) { self.class.send(name) } end end end delegate_to_class :dependency_name, :dependency_type, :source_types, :source_shortcuts def initialize(environment) self.environment = environment end def run(specfile = nil, sources = []) specfile, sources = nil, specfile if specfile.kind_of?(Array) && sources.empty? Target.new(self).tap do |target| target.precache_sources(sources) debug_named_source_cache("Pre-Cached Sources", target) specfile ||= Proc.new if block_given? receiver = Receiver.new(target) receiver.run(specfile) debug_named_source_cache("Post-Cached Sources", target) end.to_spec end def debug_named_source_cache(name, target) source_cache = target.source_cache debug { "#{name}:" } source_cache.each do |key, value| type = key[0] attributes = key[1...key.size] debug { " #{key.inspect}" } end end private def debug(*args, &block) environment.logger.debug(*args, &block) end end end librarian-0.1.1/lib/librarian/error.rb0000644000004100000410000000007112210667660017677 0ustar www-datawww-datamodule Librarian class Error < StandardError end end librarian-0.1.1/lib/librarian/resolver.rb0000644000004100000410000000406312210667660020414 0ustar www-datawww-datarequire 'librarian/resolver/implementation' require 'librarian/manifest_set' require 'librarian/resolution' module Librarian class Resolver attr_accessor :environment private :environment= def initialize(environment) self.environment = environment end def resolve(spec, partial_manifests = []) manifests = implementation(spec).resolve(partial_manifests) if manifests enforce_consistency!(spec.dependencies, manifests) manifests = sort(manifests) Resolution.new(spec.dependencies, manifests) end end private def implementation(spec) Implementation.new(self, spec) end def enforce_consistency!(dependencies, manifests) manifest_set = ManifestSet.new(manifests) return if manifest_set.in_compliance_with?(dependencies) return if manifest_set.consistent? debug { "Resolver Malfunctioned!" } errors = [] dependencies.sort_by(&:name).each do |d| m = manifests[d.name] if !m errors << ["Depends on #{d}", "Missing!"] elsif !d.satisfied_by?(m) errors << ["Depends on #{d}", "Found: #{m}"] end end unless errors.empty? errors.each do |a, b| debug { " #{a}" } debug { " #{b}" } end end manifests.values.sort_by(&:name).each do |manifest| errors = [] manifest.dependencies.sort_by(&:name).each do |d| m = manifests[d.name] if !m errors << ["Depends on: #{d}", "Missing!"] elsif !d.satisfied_by?(m) errors << ["Depends on: #{d}", "Found: #{m}"] end end unless errors.empty? debug { " #{manifest}" } errors.each do |a, b| debug { " #{a}" } debug { " #{b}" } end end end raise Error, "Resolver Malfunctioned!" end def sort(manifests) ManifestSet.sort(manifests) end def debug(*args, &block) environment.logger.debug(*args, &block) end end end librarian-0.1.1/lib/librarian/config/0000755000004100000410000000000012210667660017470 5ustar www-datawww-datalibrarian-0.1.1/lib/librarian/config/hash_source.rb0000644000004100000410000000112612210667660022320 0ustar www-datawww-datarequire "librarian/source" module Librarian module Config class HashSource < Source attr_accessor :name, :raw private :name=, :raw= def initialize(adapter_name, options = { }) super self.name = options.delete(:name) or raise ArgumentError, "must provide name" self.raw = options.delete(:raw) or raise ArgumentError, "must provide raw" end def to_s name end private def load translate_raw_to_config(raw) end def save(config) raise Error, "nonsense!" end end end end librarian-0.1.1/lib/librarian/config/source.rb0000644000004100000410000000714712210667660021326 0ustar www-datawww-datarequire "librarian/error" module Librarian module Config class Source RAW_KEY_SUFFIX_VALIDITY_PATTERN = /\A[A-Z0-9_]+\z/ CONFIG_KEY_VALIDITY_PATTERN = /\A[a-z][a-z0-9\-]+(?:\.[a-z0-9\-]+)*\z/ class << self def raw_key_suffix_validity_pattern RAW_KEY_SUFFIX_VALIDITY_PATTERN end def config_key_validity_pattern CONFIG_KEY_VALIDITY_PATTERN end end attr_accessor :adapter_name private :adapter_name= def initialize(adapter_name, options = { }) self.adapter_name = adapter_name self.forbidden_keys = options.delete(:forbidden_keys) || [] end def [](key) load! data[key] end def []=(key, value) key_permitted?(key) or raise Error, "key not permitted: #{key.inspect}" value_permitted?(key, value) or raise Error, "value for key #{key.inspect} not permitted: #{value.inspect}" load! if value.nil? data.delete(key) else data[key] = value end save(data) end def keys load! data.keys end private attr_accessor :data, :forbidden_keys def load! self.data = load unless data end def key_permitted?(key) String === key && config_key_validity_pattern === key && !forbidden_keys.any?{|k| k === key} end def value_permitted?(key, value) return true if value.nil? String === value end def raw_key_valid?(key) return false unless key.start_with?(raw_key_prefix) suffix = key[raw_key_prefix.size..-1] raw_key_suffix_validity_pattern =~ suffix end def raw_key_suffix_validity_pattern self.class.raw_key_suffix_validity_pattern end def config_key_valid?(key) config_key_validity_pattern === key end def config_key_validity_pattern self.class.config_key_validity_pattern end def raw_key_prefix @key_prefix ||= "LIBRARIAN_#{adapter_name.upcase}_" end def assert_raw_keys_valid!(raw) bad_keys = raw.keys.reject{|k| raw_key_valid?(k)} unless bad_keys.empty? config_path_s = config_path.to_s.inspect bad_keys_s = bad_keys.map(&:inspect).join(", ") raise Error, "config #{to_s} has bad keys: #{bad_keys_s}" end end def assert_config_keys_valid!(config) bad_keys = config.keys.reject{|k| config_key_valid?(k)} unless bad_keys.empty? bad_keys_s = bad_keys.map(&:inspect).join(", ") raise Error, "config has bad keys: #{bad_keys_s}" end end def assert_values_valid!(data) bad_data = data.reject{|k, v| String === v} bad_keys = bad_data.keys unless bad_keys.empty? bad_keys_s = bad_keys.map(&:inspect).join(", ") raise Error, "config has bad values for keys: #{bad_keys_s}" end end def translate_raw_to_config(raw) assert_raw_keys_valid!(raw) assert_values_valid!(raw) Hash[raw.map do |key, value| key = key[raw_key_prefix.size .. -1] key = key.downcase.gsub(/__/, ".").gsub(/_/, "-") [key, value] end] end def translate_config_to_raw(config) assert_config_keys_valid!(config) assert_values_valid!(config) Hash[config.map do |key, value| key = key.gsub(/\./, "__").gsub(/\-/, "_").upcase key = "#{raw_key_prefix}#{key}" [key, value] end] end end end end librarian-0.1.1/lib/librarian/config/database.rb0000644000004100000410000001177712210667660021576 0ustar www-datawww-datarequire "pathname" require "librarian/config/file_source" require "librarian/config/hash_source" module Librarian module Config class Database class << self def library name.split("::").first.downcase end end attr_accessor :adapter_name private :adapter_name= attr_accessor :root, :assigned_specfile_name private :root=, :assigned_specfile_name= attr_accessor :underlying_env, :underlying_pwd, :underlying_home private :underlying_env=, :underlying_pwd=, :underlying_home= def initialize(adapter_name, options = { }) self.adapter_name = adapter_name or raise ArgumentError, "must provide adapter_name" options[:project_path] || options[:pwd] or raise ArgumentError, "must provide project_path or pwd" self.root = options[:project_path] && Pathname(options[:project_path]) self.assigned_specfile_name = options[:specfile_name] self.underlying_env = options[:env] or raise ArgumentError, "must provide env" self.underlying_pwd = options[:pwd] && Pathname(options[:pwd]) self.underlying_home = options[:home] && Pathname(options[:home]) end def global memo(__method__) { new_file_source(global_config_path) } end def env memo(__method__) { HashSource.new(adapter_name, :name => "environment", :raw => env_source_data) } end def local memo(__method__) { new_file_source(local_config_path) } end def [](key, scope = nil) case scope when "local", :local then local[key] when "env", :env then env[key] when "global", :global then global[key] when nil then local[key] || env[key] || global[key] else raise Error, "bad scope" end end def []=(key, scope, value) case scope when "local", :local then local[key] = value when "global", :global then global[key] = value else raise Error, "bad scope" end end def keys [local, env, global].inject([]){|a, e| a.concat(e.keys) ; a}.sort.uniq end def project_path root || specfile_path.dirname end def specfile_path if root root + (assigned_specfile_name || default_specfile_name) else env_specfile_path || default_specfile_path end end def specfile_name specfile_path.basename.to_s end def lockfile_path project_path + lockfile_name end def lockfile_name "#{specfile_name}.lock" end private def new_file_source(config_path) return unless config_path FileSource.new(adapter_name, :config_path => config_path, :forbidden_keys => [config_key, specfile_key] ) end def global_config_path env_global_config_path || default_global_config_path end def env_global_config_path memo(__method__) { env[config_key] } end def default_global_config_path underlying_home && underlying_home + config_name end def local_config_path root_local_config_path || env_local_config_path || default_local_config_path end def root_local_config_path root && root + config_name end def env_specfile_path memo(__method__) do path = env[specfile_key] path && Pathname(path) end end def default_specfile_path default_project_root_path + (assigned_specfile_name || default_specfile_name) end def env_local_config_path return unless env_specfile_path env_specfile_path.dirname + config_name end def default_local_config_path default_project_root_path + config_name end def default_project_root_path if root root else path = underlying_pwd path = path.dirname until project_root_path?(path) || path.dirname == path project_root_path?(path) ? path : underlying_pwd end end def project_root_path?(path) File.file?(path + default_specfile_name) end def config_key "config" end def specfile_key "#{adapter_name}file" end def default_specfile_name "#{adapter_name.capitalize}file" end def library self.class.library end def config_name_prefix ".#{library}" end def config_name File.join(*[config_name_prefix, adapter_name, "config"]) end def raw_key_prefix "#{library.upcase}_#{adapter_name.upcase}_" end def env_source_data prefix = raw_key_prefix data = underlying_env.dup data.reject!{|k, _| !k.start_with?(prefix) || k.size <= prefix.size} data end def memo(key) key = "@#{key}" instance_variable_set(key, yield) unless instance_variable_defined?(key) instance_variable_get(key) end end end end librarian-0.1.1/lib/librarian/config/file_source.rb0000644000004100000410000000176312210667660022323 0ustar www-datawww-datarequire "yaml" require "librarian/config/source" module Librarian module Config class FileSource < Source attr_accessor :config_path private :config_path= def initialize(adapter_name, options = { }) super self.config_path = options.delete(:config_path) or raise ArgumentError, "must provide config_path" end def to_s config_path end private def load return { } unless File.file?(config_path) raw = YAML.load_file(config_path) return { } unless Hash === raw translate_raw_to_config(raw) end def save(config) raw = translate_config_to_raw(config) if config.empty? File.delete(config_path) if File.file?(config_path) else config_dir = File.dirname(config_path) FileUtils.mkpath(config_dir) unless File.directory?(config_dir) File.open(config_path, "wb"){|f| YAML.dump(raw, f)} end end end end end librarian-0.1.1/lib/librarian/action/0000755000004100000410000000000012210667660017500 5ustar www-datawww-datalibrarian-0.1.1/lib/librarian/action/resolve.rb0000644000004100000410000000200112210667660021475 0ustar www-datawww-datarequire "librarian/resolver" require "librarian/spec_change_set" require "librarian/action/base" require "librarian/action/persist_resolution_mixin" module Librarian module Action class Resolve < Base include PersistResolutionMixin def run if force? || !lockfile_path.exist? spec = specfile.read manifests = [] else lock = lockfile.read spec = specfile.read(lock.sources) changes = spec_change_set(spec, lock) if changes.same? debug { "The specfile is unchanged: nothing to do." } return end manifests = changes.analyze end resolution = resolver.resolve(spec, manifests) persist_resolution(resolution) end private def force? options[:force] end def resolver Resolver.new(environment) end def spec_change_set(spec, lock) SpecChangeSet.new(environment, spec, lock) end end end end librarian-0.1.1/lib/librarian/action/base.rb0000644000004100000410000000064012210667660020737 0ustar www-datawww-datamodule Librarian module Action class Base attr_accessor :environment private :environment= attr_accessor :options private :options= def initialize(environment, options = { }) self.environment = environment self.options = options end private def debug(*args, &block) environment.logger.debug(*args, &block) end end end end librarian-0.1.1/lib/librarian/action/persist_resolution_mixin.rb0000644000004100000410000000226212210667660025207 0ustar www-datawww-datarequire "librarian/error" require "librarian/spec_change_set" module Librarian module Action module PersistResolutionMixin private def persist_resolution(resolution) resolution && resolution.correct? or raise Error, "Could not resolve the dependencies." lockfile_text = lockfile.save(resolution) debug { "Bouncing #{lockfile_name}" } bounced_lockfile_text = lockfile.save(lockfile.load(lockfile_text)) unless bounced_lockfile_text == lockfile_text debug { "lockfile_text: \n#{lockfile_text}" } debug { "bounced_lockfile_text: \n#{bounced_lockfile_text}" } raise Error, "Cannot bounce #{lockfile_name}!" end lockfile_path.open('wb') { |f| f.write(lockfile_text) } end def specfile_name environment.specfile_name end def lockfile_name environment.lockfile_name end def specfile_path environment.specfile_path end def lockfile_path environment.lockfile_path end def specfile environment.specfile end def lockfile environment.lockfile end end end end librarian-0.1.1/lib/librarian/action/clean.rb0000644000004100000410000000152212210667660021107 0ustar www-datawww-datarequire "librarian/action/base" module Librarian module Action class Clean < Base def run clean_cache_path clean_install_path end private def clean_cache_path if cache_path.exist? debug { "Deleting #{project_relative_path_to(cache_path)}" } cache_path.rmtree end end def clean_install_path if install_path.exist? install_path.children.each do |c| debug { "Deleting #{project_relative_path_to(c)}" } c.rmtree unless c.file? end end end def cache_path environment.cache_path end def install_path environment.install_path end def project_relative_path_to(path) environment.project_relative_path_to(path) end end end end librarian-0.1.1/lib/librarian/action/ensure.rb0000644000004100000410000000057612210667660021336 0ustar www-datawww-datarequire "librarian/error" require "librarian/action/base" module Librarian module Action class Ensure < Base def run raise Error, "Cannot find #{specfile_name}!" unless project_path end private def specfile_name environment.specfile_name end def project_path environment.project_path end end end end librarian-0.1.1/lib/librarian/action/install.rb0000644000004100000410000000351512210667660021477 0ustar www-datawww-datarequire "librarian/manifest_set" require "librarian/spec_change_set" require "librarian/action/base" module Librarian module Action class Install < Base def run check_preconditions perform_installation end private def check_preconditions check_specfile check_lockfile check_consistent end def check_specfile raise Error, "#{specfile_name} missing!" unless specfile_path.exist? end def check_lockfile raise Error, "#{lockfile_name} missing!" unless lockfile_path.exist? end def check_consistent raise Error, "#{specfile_name} and #{lockfile_name} are out of sync!" unless spec_consistent_with_lock? end def perform_installation manifests = sorted_manifests create_install_path install_manifests(manifests) end def create_install_path install_path.rmtree if install_path.exist? install_path.mkpath end def install_manifests(manifests) manifests.each do |manifest| manifest.install! end end def sorted_manifests ManifestSet.sort(lock.manifests) end def specfile_name environment.specfile_name end def specfile_path environment.specfile_path end def lockfile_name environment.lockfile_name end def lockfile_path environment.lockfile_path end def spec environment.spec end def lock environment.lock end def spec_change_set(spec, lock) SpecChangeSet.new(environment, spec, lock) end def spec_consistent_with_lock? spec_change_set(spec, lock).same? end def install_path environment.install_path end end end end librarian-0.1.1/lib/librarian/action/update.rb0000644000004100000410000000232212210667660021306 0ustar www-datawww-datarequire "librarian/manifest_set" require "librarian/resolver" require "librarian/spec_change_set" require "librarian/action/base" require "librarian/action/persist_resolution_mixin" module Librarian module Action class Update < Base include PersistResolutionMixin def run unless lockfile_path.exist? raise Error, "Lockfile missing!" end previous_resolution = lockfile.load(lockfile_path.read) spec = specfile.read(previous_resolution.sources) changes = spec_change_set(spec, previous_resolution) manifests = changes.same? ? previous_resolution.manifests : changes.analyze partial_manifests = ManifestSet.deep_strip(manifests, dependency_names) unpinnable_sources = previous_resolution.sources - partial_manifests.map(&:source) unpinnable_sources.each(&:unpin!) resolution = resolver.resolve(spec, partial_manifests) persist_resolution(resolution) end private def dependency_names options[:names] end def resolver Resolver.new(environment) end def spec_change_set(spec, lock) SpecChangeSet.new(environment, spec, lock) end end end end librarian-0.1.1/lib/librarian/rspec/0000755000004100000410000000000012210667660017337 5ustar www-datawww-datalibrarian-0.1.1/lib/librarian/rspec/support/0000755000004100000410000000000012210667660021053 5ustar www-datawww-datalibrarian-0.1.1/lib/librarian/rspec/support/cli_macro.rb0000644000004100000410000000563112210667660023335 0ustar www-datawww-datarequire "json" require "pathname" require "securerandom" require "stringio" require "thor" require "librarian/helpers" module Librarian module RSpec module Support module CliMacro class FakeShell < Thor::Shell::Basic def stdout @stdout ||= StringIO.new end def stderr @stderr ||= StringIO.new end def stdin raise "unsupported" end end class FileMatcher attr_accessor :rel_path, :content, :type, :base_path def initialize(rel_path, content, options = { }) self.rel_path = rel_path self.content = content self.type = options[:type] end def full_path @full_path ||= base_path + rel_path end def actual_content @actual_content ||= begin s = full_path.read s = JSON.parse(s) if type == :json s end end def matches?(base_path) base_path = Pathname(base_path) unless Pathname === base_path self.base_path = base_path full_path.file? && (!content || actual_content == content) end end def self.included(base) base.instance_exec do let(:project_path) do project_path = Pathname.new(__FILE__).expand_path project_path = project_path.dirname until project_path.join("Rakefile").exist? project_path end let(:tmp) { project_path.join("tmp/spec/cli") } let(:pwd) { tmp + SecureRandom.hex(8) } before { tmp.mkpath } before { pwd.mkpath } after { tmp.rmtree } end end def cli!(*args) @shell = FakeShell.new @exit_status = Dir.chdir(pwd) do described_class.with_environment do described_class.returning_status do described_class.start args, :shell => @shell end end end end def write_file!(path, content) path = pwd.join(path) path.dirname.mkpath path.open("wb"){|f| f.write(content)} end def write_json_file!(path, content) write_file! path, JSON.dump(content) end def strip_heredoc(text) Librarian::Helpers.strip_heredoc(text) end def shell @shell end def stdout shell.stdout.string end def stderr shell.stderr.string end def exit_status @exit_status end def have_file(rel_path, content = nil) FileMatcher.new(rel_path, content) end def have_json_file(rel_path, content) FileMatcher.new(rel_path, content, :type => :json) end end end end end librarian-0.1.1/lib/librarian/resolver/0000755000004100000410000000000012210667660020064 5ustar www-datawww-datalibrarian-0.1.1/lib/librarian/resolver/implementation.rb0000644000004100000410000001470412210667660023444 0ustar www-datawww-datarequire 'librarian/dependency' module Librarian class Resolver class Implementation class MultiSource attr_accessor :sources def initialize(sources) self.sources = sources end def manifests(name) sources.reverse.map{|source| source.manifests(name)}.flatten(1).compact end def to_s "(no source specified)" end end attr_accessor :resolver, :spec private :resolver=, :spec= def initialize(resolver, spec) self.resolver = resolver self.spec = spec @level = 0 end def resolve(manifests) manifests = index_by(manifests, &:name) if manifests.kind_of?(Array) addtl = spec.dependencies + sourced_dependencies_for_manifests(manifests) recursive_resolve([], manifests, [], addtl) end private def find_inconsistency(dep, deps, mans) m = mans[dep.name] dep.satisfied_by?(m) or return m if m deps.find{|d| !dep.consistent_with?(d)} end def recursive_resolve(dependencies, manifests, queue, addtl) dependencies = dependencies.dup manifests = manifests.dup queue = queue.dup return unless enqueue_dependencies(queue, addtl, dependencies, manifests) return unless shift_resolved_enqueued_dependencies(dependencies, manifests, queue) return manifests if queue.empty? dependency = queue.shift dependencies << dependency all_deps = dependencies + queue resolving_dependency_map_find_manifests(dependency) do |manifest| next unless check_manifest(manifest, all_deps) m = manifests.merge(dependency.name => manifest) a = sourced_dependencies_for_manifest(manifest) recursive_resolve(dependencies, m, queue, a) end end # When using this method, you are required to check the return value. # Returns +true+ if the enqueueables could all be enqueued. # Returns +false+ if there was an inconsistency when trying to enqueue one # or more of them. # This modifies +queue+ but does not modify any other arguments. def enqueue_dependencies(queue, enqueueables, dependencies, manifests) enqueueables.each do |d| if q = find_inconsistency(d, dependencies + queue, manifests) debug_conflict d, q return false end debug_schedule d queue << d end true end # When using this method, you are required to check the return value. # Returns +true+ if the resolved enqueued dependencies at the front of the # queue could all be moved to the resolved dependencies list. # Returns +false+ if there was an inconsistency when trying to move one or # more of them. # This modifies +queue+ and +dependencies+. def shift_resolved_enqueued_dependencies(dependencies, manifests, queue) all_deps = dependencies + queue while (dependency = queue.first) && manifests[dependency.name] if q = find_inconsistency(dependency, all_deps, manifests) debug_conflict dependency, q return false end dependencies << queue.shift end true end # When using this method, you are required to check the return value. # Returns +true+ if the manifest satisfies all of the dependencies. # Returns +false+ if there was a dependency that the manifest does not # satisfy. def check_manifest(manifest, all_deps) related = all_deps.select{|d| d.name == manifest.name} if q = related.find{|d| !d.satisfied_by?(manifest)} debug_conflict manifest, q return false end true end def default_source @default_source ||= MultiSource.new(spec.sources) end def dependency_source_map @dependency_source_map ||= Hash[spec.dependencies.map{|d| [d.name, d.source]}] end def sourced_dependency_for(dependency) return dependency if dependency.source source = dependency_source_map[dependency.name] || default_source Dependency.new(dependency.name, dependency.requirement, source) end def sourced_dependencies_for_manifest(manifest) manifest.dependencies.map{|d| sourced_dependency_for(d)} end def sourced_dependencies_for_manifests(manifests) manifests = manifests.values if manifests.kind_of?(Hash) manifests.map{|m| sourced_dependencies_for_manifest(m)}.flatten(1) end def resolving_dependency_map_find_manifests(dependency) scope_resolving_dependency dependency do map_find(dependency.manifests) do |manifest| scope_checking_manifest dependency, manifest do yield manifest end end end end def scope_resolving_dependency(dependency) debug { "Resolving #{dependency}" } resolution = nil scope do scope_checking_manifests do resolution = yield end if resolution debug { "Resolved #{dependency}" } else debug { "Failed to resolve #{dependency}" } end end resolution end def scope_checking_manifests debug { "Checking manifests" } scope do yield end end def scope_checking_manifest(dependency, manifest) debug { "Checking #{manifest}" } resolution = nil scope do resolution = yield if resolution debug { "Resolved #{dependency} at #{manifest}" } else debug { "Backtracking from #{manifest}" } end end resolution end def debug_schedule(dependency) debug { "Scheduling #{dependency}" } end def debug_conflict(dependency, conflict) debug { "Conflict between #{dependency} and #{conflict}" } end def map_find(enum) enum.each do |obj| res = yield(obj) res.nil? or return res end nil end def index_by(enum) Hash[enum.map{|obj| [yield(obj), obj]}] end def scope @level += 1 yield ensure @level -= 1 end def debug environment.logger.debug { ' ' * @level + yield } end def environment resolver.environment end end end end librarian-0.1.1/lib/librarian/posix.rb0000644000004100000410000000627412210667660017723 0ustar www-datawww-datarequire "open3" require "librarian/error" module Librarian module Posix class << self # Cross-platform way of finding an executable in the $PATH. # # which('ruby') #=> /usr/bin/ruby # # From: # https://github.com/defunkt/hub/commit/353031307e704d860826fc756ff0070be5e1b430#L2R173 def which(cmd) exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(';') : [''] ENV["PATH"].split(File::PATH_SEPARATOR).each do |path| path = File.expand_path(path) exts.each do |ext| exe = File.join(path, cmd + ext) return exe if File.file?(exe) && File.executable?(exe) end end nil end def which!(cmd) which(cmd) or raise Error, "cannot find #{cmd}" end end class CommandFailure < Error class << self def raise!(command, status, message) ex = new(message) ex.command = command ex.status = status ex.set_backtrace caller raise ex end end attr_accessor :command, :status end class << self if defined?(JRuby) # built with jruby-1.7.4 in mind def run!(command, options = { }) out, err = nil, nil chdir = options[:chdir].to_s if options[:chdir] env = options[:env] || { } old_env = Hash[env.keys.map{|k| [k, ENV[k]]}] begin ENV.update env Dir.chdir(chdir || Dir.pwd) do IO.popen3(*command) do |i, o, e| i.close out, err = o.read, e.read end end ensure ENV.update old_env end $?.success? or CommandFailure.raise! command, $?, err out end else if RUBY_VERSION < "1.9" def run!(command, options = { }) i, o, e = IO.pipe, IO.pipe, IO.pipe pid = fork do $stdin.reopen i[0] $stdout.reopen o[1] $stderr.reopen e[1] [i[1], i[0], o[0], e[0]].each &:close ENV.update options[:env] || { } Dir.chdir options[:chdir].to_s if options[:chdir] exec *command end [i[0], i[1], o[1], e[1]].each &:close Process.waitpid pid $?.success? or CommandFailure.raise! command, $?, e[0].read o[0].read ensure [i, o, e].flatten(1).each{|io| io.close unless io.closed?} end else def run!(command, options = { }) i, o, e = IO.pipe, IO.pipe, IO.pipe opts = {:in => i[0], :out => o[1], :err => e[1]} opts[:chdir] = options[:chdir].to_s if options[:chdir] command = command.dup command.unshift options[:env] || { } command.push opts pid = Process.spawn(*command) [i[0], i[1], o[1], e[1]].each &:close Process.waitpid pid $?.success? or CommandFailure.raise! command, $?, e[0].read o[0].read ensure [i, o, e].flatten(1).each{|io| io.close unless io.closed?} end end end end end end librarian-0.1.1/lib/librarian/spec.rb0000644000004100000410000000036112210667660017502 0ustar www-datawww-datamodule Librarian class Spec attr_accessor :sources, :dependencies private :sources=, :dependencies= def initialize(sources, dependencies) self.sources = sources self.dependencies = dependencies end end end librarian-0.1.1/lib/librarian/helpers.rb0000644000004100000410000000132112210667660020207 0ustar www-datawww-datamodule Librarian # PRIVATE # # Adapters must not rely on these methods since they will change. # # Adapters requiring similar methods ought to re-implement them. module Helpers extend self # [active_support/core_ext/string/strip] def strip_heredoc(string) indent = string.scan(/^[ \t]*(?=\S)/).min indent = indent.respond_to?(:size) ? indent.size : 0 string.gsub(/^[ \t]{#{indent}}/, '') end # [active_support/inflector/methods] def camel_cased_to_dasherized(camel_cased_word) word = camel_cased_word.to_s.dup word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1-\2') word.gsub!(/([a-z\d])([A-Z])/,'\1-\2') word.downcase! word end end end librarian-0.1.1/lib/librarian/support/0000755000004100000410000000000012210667660017737 5ustar www-datawww-datalibrarian-0.1.1/lib/librarian/support/abstract_method.rb0000644000004100000410000000071012210667660023425 0ustar www-datawww-datamodule Librarian module Support module AbstractMethod class << self def included(base) base.extend ClassMethods end end module ClassMethods def abstract_method(*names) names.reject{|name| respond_to?(name)}.each do |name, *args| define_method(name) { raise Exception, "Method #{self.class.name}##{name} is abstract!" } end end end end end end