librarianp-0.6.4/0000755000004100000410000000000013172517676013713 5ustar www-datawww-datalibrarianp-0.6.4/Rakefile0000644000004100000410000000122313172517676015356 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 librarianp-0.6.4/Gemfile.lock0000644000004100000410000001403613172517676016141 0ustar www-datawww-dataPATH remote: . specs: librarianp (0.6.4) thor (~> 0.15) GEM remote: https://rubygems.org/ specs: diff-lcs (1.2.5) fakefs (0.4.2) ffi2-generators (0.1.1) json (1.8.2) rake (10.4.2) rspec (2.99.0) rspec-core (~> 2.99.0) rspec-expectations (~> 2.99.0) rspec-mocks (~> 2.99.0) rspec-core (2.99.2) rspec-expectations (2.99.2) diff-lcs (>= 1.1.3, < 2.0) rspec-mocks (2.99.3) rubysl (2.1.0) rubysl-abbrev (~> 2.0) rubysl-base64 (~> 2.0) rubysl-benchmark (~> 2.0) rubysl-bigdecimal (~> 2.0) rubysl-cgi (~> 2.0) rubysl-cgi-session (~> 2.0) rubysl-cmath (~> 2.0) rubysl-complex (~> 2.0) rubysl-continuation (~> 2.0) rubysl-coverage (~> 2.0) rubysl-csv (~> 2.0) rubysl-curses (~> 2.0) rubysl-date (~> 2.0) rubysl-delegate (~> 2.0) rubysl-digest (~> 2.0) rubysl-drb (~> 2.0) rubysl-e2mmap (~> 2.0) rubysl-english (~> 2.0) rubysl-enumerator (~> 2.0) rubysl-erb (~> 2.0) rubysl-etc (~> 2.0) rubysl-expect (~> 2.0) rubysl-fcntl (~> 2.0) rubysl-fiber (~> 2.0) rubysl-fileutils (~> 2.0) rubysl-find (~> 2.0) rubysl-forwardable (~> 2.0) rubysl-getoptlong (~> 2.0) rubysl-gserver (~> 2.0) rubysl-io-console (~> 2.0) rubysl-io-nonblock (~> 2.0) rubysl-io-wait (~> 2.0) rubysl-ipaddr (~> 2.0) rubysl-irb (~> 2.1) rubysl-logger (~> 2.0) rubysl-mathn (~> 2.0) rubysl-matrix (~> 2.0) rubysl-mkmf (~> 2.0) rubysl-monitor (~> 2.0) rubysl-mutex_m (~> 2.0) rubysl-net-ftp (~> 2.0) rubysl-net-http (~> 2.0) rubysl-net-imap (~> 2.0) rubysl-net-pop (~> 2.0) rubysl-net-protocol (~> 2.0) rubysl-net-smtp (~> 2.0) rubysl-net-telnet (~> 2.0) rubysl-nkf (~> 2.0) rubysl-observer (~> 2.0) rubysl-open-uri (~> 2.0) rubysl-open3 (~> 2.0) rubysl-openssl (~> 2.0) rubysl-optparse (~> 2.0) rubysl-ostruct (~> 2.0) rubysl-pathname (~> 2.0) rubysl-prettyprint (~> 2.0) rubysl-prime (~> 2.0) rubysl-profile (~> 2.0) rubysl-profiler (~> 2.0) rubysl-pstore (~> 2.0) rubysl-pty (~> 2.0) rubysl-rational (~> 2.0) rubysl-resolv (~> 2.0) rubysl-rexml (~> 2.0) rubysl-rinda (~> 2.0) rubysl-rss (~> 2.0) rubysl-scanf (~> 2.0) rubysl-securerandom (~> 2.0) rubysl-set (~> 2.0) rubysl-shellwords (~> 2.0) rubysl-singleton (~> 2.0) rubysl-socket (~> 2.0) rubysl-stringio (~> 2.0) rubysl-strscan (~> 2.0) rubysl-sync (~> 2.0) rubysl-syslog (~> 2.0) rubysl-tempfile (~> 2.0) rubysl-thread (~> 2.0) rubysl-thwait (~> 2.0) rubysl-time (~> 2.0) rubysl-timeout (~> 2.0) rubysl-tmpdir (~> 2.0) rubysl-tsort (~> 2.0) rubysl-un (~> 2.0) rubysl-uri (~> 2.0) rubysl-weakref (~> 2.0) rubysl-webrick (~> 2.0) rubysl-xmlrpc (~> 2.0) rubysl-yaml (~> 2.0) rubysl-zlib (~> 2.0) rubysl-abbrev (2.0.4) rubysl-base64 (2.0.0) rubysl-benchmark (2.0.1) rubysl-bigdecimal (2.0.2) rubysl-cgi (2.0.1) rubysl-cgi-session (2.0.1) rubysl-cmath (2.0.0) rubysl-complex (2.0.0) rubysl-continuation (2.0.0) rubysl-coverage (2.0.3) rubysl-csv (2.0.2) rubysl-english (~> 2.0) rubysl-curses (2.0.1) rubysl-date (2.0.9) rubysl-delegate (2.0.1) rubysl-digest (2.0.3) rubysl-drb (2.0.1) rubysl-e2mmap (2.0.0) rubysl-english (2.0.0) rubysl-enumerator (2.0.0) rubysl-erb (2.0.2) rubysl-etc (2.0.3) ffi2-generators (~> 0.1) rubysl-expect (2.0.0) rubysl-fcntl (2.0.4) ffi2-generators (~> 0.1) rubysl-fiber (2.0.0) rubysl-fileutils (2.0.3) rubysl-find (2.0.1) rubysl-forwardable (2.0.1) rubysl-getoptlong (2.0.0) rubysl-gserver (2.0.0) rubysl-socket (~> 2.0) rubysl-thread (~> 2.0) rubysl-io-console (2.0.0) rubysl-io-nonblock (2.0.0) rubysl-io-wait (2.0.0) rubysl-ipaddr (2.0.0) rubysl-irb (2.1.1) rubysl-e2mmap (~> 2.0) rubysl-mathn (~> 2.0) rubysl-thread (~> 2.0) rubysl-logger (2.1.0) rubysl-mathn (2.0.0) rubysl-matrix (2.1.0) rubysl-e2mmap (~> 2.0) rubysl-mkmf (2.0.1) rubysl-fileutils (~> 2.0) rubysl-shellwords (~> 2.0) rubysl-monitor (2.0.0) rubysl-mutex_m (2.0.0) rubysl-net-ftp (2.0.1) rubysl-net-http (2.0.4) rubysl-cgi (~> 2.0) rubysl-erb (~> 2.0) rubysl-singleton (~> 2.0) rubysl-net-imap (2.0.1) rubysl-net-pop (2.0.1) rubysl-net-protocol (2.0.1) rubysl-net-smtp (2.0.1) rubysl-net-telnet (2.0.0) rubysl-nkf (2.0.1) rubysl-observer (2.0.0) rubysl-open-uri (2.0.0) rubysl-open3 (2.0.0) rubysl-openssl (2.2.1) rubysl-optparse (2.0.1) rubysl-shellwords (~> 2.0) rubysl-ostruct (2.0.4) rubysl-pathname (2.1.0) rubysl-prettyprint (2.0.3) rubysl-prime (2.0.1) rubysl-profile (2.0.0) rubysl-profiler (2.0.1) rubysl-pstore (2.0.0) rubysl-pty (2.0.3) rubysl-rational (2.0.1) rubysl-resolv (2.1.2) rubysl-rexml (2.0.4) rubysl-rinda (2.0.1) rubysl-rss (2.0.0) rubysl-scanf (2.0.0) rubysl-securerandom (2.0.0) rubysl-set (2.0.1) rubysl-shellwords (2.0.0) rubysl-singleton (2.0.0) rubysl-socket (2.0.1) rubysl-stringio (2.0.0) rubysl-strscan (2.0.0) rubysl-sync (2.0.0) rubysl-syslog (2.1.0) ffi2-generators (~> 0.1) rubysl-tempfile (2.0.1) rubysl-thread (2.0.3) rubysl-thwait (2.0.0) rubysl-time (2.0.3) rubysl-timeout (2.0.0) rubysl-tmpdir (2.0.1) rubysl-tsort (2.0.1) rubysl-un (2.0.0) rubysl-fileutils (~> 2.0) rubysl-optparse (~> 2.0) rubysl-uri (2.0.0) rubysl-weakref (2.0.0) rubysl-webrick (2.0.0) rubysl-xmlrpc (2.0.0) rubysl-yaml (2.1.0) rubysl-zlib (2.0.1) thor (0.19.1) PLATFORMS ruby DEPENDENCIES fakefs json librarianp! rake rspec (~> 2.14) rubysl librarianp-0.6.4/Gemfile0000644000004100000410000000022113172517676015201 0ustar www-datawww-datasource "https://rubygems.org" # Specify your gem's dependencies in librarian.gemspec gemspec gem "fakefs" gem "rubysl", :platforms => %w[rbx] librarianp-0.6.4/.rspec0000644000004100000410000000003713172517676015030 0ustar www-datawww-data--color --format documentation librarianp-0.6.4/LICENSE.txt0000644000004100000410000000211413172517676015534 0ustar www-datawww-dataCopyright (c) 2011 ApplicationsOnline, LLC. and Carlos Sanchez 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. librarianp-0.6.4/spec/0000755000004100000410000000000013172517676014645 5ustar www-datawww-datalibrarianp-0.6.4/spec/unit/0000755000004100000410000000000013172517676015624 5ustar www-datawww-datalibrarianp-0.6.4/spec/unit/manifest_spec.rb0000644000004100000410000000160713172517676020775 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 librarianp-0.6.4/spec/unit/algorithms_spec.rb0000644000004100000410000000761413172517676021344 0ustar www-datawww-datarequire "librarian/algorithms" module Librarian module Algorithms describe AdjacencyListDirectedGraph do describe :cyclic? do subject(:result) { described_class.cyclic?(graph) } context "with an empty graph" do let(:graph) { { } } it { should be false } end context "with a 1-node acyclic graph" do let(:graph) { { ?a => nil } } it { should be false } end context "with a 1-node cyclic graph" do let(:graph) { { ?a => [?a] } } it { should be true } end context "with a 2-node no-edge graph" do let(:graph) { { ?a => nil, ?b => nil } } it { should be false } end context "with a 2-node acyclic graph" do let(:graph) { { ?a => [?b], ?b => nil } } it { should be false } end context "with a 2-node cyclic graph" do let(:graph) { { ?a => [?b], ?b => [?a] } } it { should be true } end context "with a 2-scc graph" do let(:graph) { { ?a => [?b], ?b => [?a], ?c => [?d, ?b], ?d => [?c] } } it { should be true } end end describe :feedback_arc_set do subject(:result) { described_class.feedback_arc_set(graph) } context "with an empty graph" do let(:graph) { { } } it { should be_empty } end context "with a 1-node acyclic graph" do let(:graph) { { ?a => nil } } it { should be_empty } end context "with a 1-node cyclic graph" do let(:graph) { { ?a => [?a] } } it { should be == [[?a, ?a]] } end context "with a 2-node no-edge graph" do let(:graph) { { ?a => nil, ?b => nil } } it { should be_empty } end context "with a 2-node acyclic graph" do let(:graph) { { ?a => [?b], ?b => nil } } it { should be_empty } end context "with a 2-node cyclic graph" do let(:graph) { { ?a => [?b], ?b => [?a] } } it { should be == [[?a, ?b]] } # based on the explicit sort end context "with a 2-scc graph" do let(:graph) { { ?a => [?b], ?b => [?a], ?c => [?d, ?b], ?d => [?c] } } it { should be == [[?a, ?b], [?c, ?d]] } end end describe :tsort_cyclic do subject(:result) { described_class.tsort_cyclic(graph) } context "with an empty graph" do let(:graph) { { } } it { should be == [] } end context "with a 1-node acyclic graph" do let(:graph) { { ?a => nil } } it { should be == [?a] } end context "with a 1-node cyclic graph" do let(:graph) { { ?a => [?a] } } it { should be == [?a] } end context "with a 2-node no-edge graph" do let(:graph) { { ?a => nil, ?b => nil } } it { should be == [?a, ?b] } end context "with a 2-node acyclic graph" do let(:graph) { { ?a => [?b], ?b => nil } } it { should be == [?b, ?a] } # based on the explicit sort end context "with a 2-node cyclic graph" do let(:graph) { { ?a => [?b], ?b => [?a] } } it { should be == [?a, ?b] } # based on the explicit sort end context "with a 2-scc graph" do let(:graph) { { ?a => [?b], ?b => [?a], ?c => [?d, ?b], ?d => [?c] } } it { should be == [?a, ?b, ?c, ?d] } end # Dependencies are sorted alphabetically. Should they? context "should be deterministic" do let(:graph) { { ?a => [], ?b => [?c], ?c => [] } } it { should be == [?a, ?c, ?b] } end context "should be deterministic" do let(:graph) { { ?c => [], ?b => [?a], ?a => [] } } it { should be == [?a, ?b, ?c] } end end end end end librarianp-0.6.4/spec/unit/manifest/0000755000004100000410000000000013172517676017432 5ustar www-datawww-datalibrarianp-0.6.4/spec/unit/manifest/version_spec.rb0000644000004100000410000000624113172517676022461 0ustar www-datawww-datarequire "librarian/manifest" describe Librarian::Manifest::Version do describe "#inspect" do subject(:version) { described_class.new("3.2.1") } specify { expect(version.inspect).to eq "#" } end describe "version comparison" do context "when version has only two components" do it "creates a new version with only 2 version components" do v1 = described_class.new("1.0") end end context "when neither version has pre-release items" do it "compares 1.0.0 < 2.0.0" do v1 = described_class.new("1.0.0") v2 = described_class.new("2.0.0") expect(v1 <=> v2).to eq(-1) end it "compares 2.0.0 < 2.1.0" do v1 = described_class.new("2.0.0") v2 = described_class.new("2.1.0") expect(v1 <=> v2).to eq(-1) end it "compares 2.1.0 < 2.1.1" do v1 = described_class.new("2.1.0") v2 = described_class.new("2.1.1") expect(v1 <=> v2).to eq(-1) end end context "when versions have pre-release information" do it "compares 1.0.0-alpha < 1.0.0-alpha1" do v1 = described_class.new("1.0.0-alpha") v2 = described_class.new("1.0.0-alpha.1") expect(v1 <=> v2).to eq(-1) end it "compares 1.0.0-alpha.1 < 1.0.0-alpha.beta" do v1 = described_class.new("1.0.0-alpha.1") v2 = described_class.new("1.0.0-alpha.beta") expect(v1 <=> v2).to eq(-1) end it "compares 1.0.0-alpha.beta < 1.0.0-beta" do v1 = described_class.new("1.0.0-alpha.beta") v2 = described_class.new("1.0.0-beta") expect(v1 <=> v2).to eq(-1) end it "compares 1.0.0-beta < 1.0.0-beta.2" do v1 = described_class.new("1.0.0-beta") v2 = described_class.new("1.0.0-beta.2") expect(v1 <=> v2).to eq(-1) end it "compares 1.0.0-beta.2 < 1.0.0-beta.11" do v1 = described_class.new("1.0.0-beta.2") v2 = described_class.new("1.0.0-beta.11") expect(v1 <=> v2).to eq(-1) end it "compares 1.0.0-beta.11 < 1.0.0-rc.1" do v1 = described_class.new("1.0.0-beta.11") v2 = described_class.new("1.0.0-rc.1") expect(v1 <=> v2).to eq(-1) end it "compares 1.0.0-rc.1 < 1.0.0" do v1 = described_class.new("1.0.0-rc.1") v2 = described_class.new("1.0.0") expect(v1 <=> v2).to eq(-1) end end context "when an invalid version number is provided" do it "raises" do expect { described_class.new("invalidversion") }. to raise_error(ArgumentError) end end context "when a version is converted to string" do it "should be the full semver" do version = "1.0.0-beta.11+200.1.2" v1 = described_class.new(version) expect(v1.to_s).to eq(version) end it "should be the full gem version" do version = "1.0.0.a" v1 = described_class.new(version) expect(v1.to_s).to eq(version) end it "should be the two-component version" do version = "1.0" v1 = described_class.new(version) expect(v1.to_s).to eq(version) end end end end librarianp-0.6.4/spec/unit/environment_spec.rb0000644000004100000410000001373013172517676021533 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 { expect(env.adapter_module).to be nil } end describe "#adapter_name" do specify { expect(env.adapter_name).to be nil } end describe "#adapter_version" do specify { expect(env.adapter_version).to 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") expect(env.config_db.underlying_home.to_s).to eq "/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") expect(env.config_db.underlying_home.to_s).to eq 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 expect(env.http_proxy_uri).to 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 expect(env.http_proxy_uri).to eq URI("http://admin:secret@example.com") end it "should have the expected host" do expect(env.http_proxy_uri.host).to eq "example.com" end it "should have the expected user" do expect(env.http_proxy_uri.user).to eq "admin" end it "should have the expected password" do expect(env.http_proxy_uri.password).to eq "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 expect(env.http_proxy_uri).to eq 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 expect(env.net_http_class(proxied_host)).to be Net::HTTP end it "should not be marked as a proxy class" do expect(env.net_http_class(proxied_host)).to_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 expect(env.net_http_class('localhost')).to_not be_proxy_class end it "should not have the normal class" do expect(env.net_http_class(proxied_host)).to_not be Net::HTTP end it "should have a subclass the normal class" do expect(env.net_http_class(proxied_host)).to be < Net::HTTP end it "should be marked as a proxy class" do expect(env.net_http_class(proxied_host)).to 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, } expect(actual_attributes).to eq 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 expect(env.net_http_class(proxied_host)).to be Net::HTTP end it "should not be marked as a proxy class" do expect(env.net_http_class(proxied_host)).to_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 expect(env.net_http_class(proxied_host)).to be Net::HTTP end it "should not be marked as a proxy class" do expect(env.net_http_class(proxied_host)).to_not be_proxy_class end end context "with localhost" do let(:proxied_host) { "localhost" } it "should have the normal class" do expect(env.net_http_class(proxied_host)).to be Net::HTTP end it "should not be marked as a proxy class" do expect(env.net_http_class(proxied_host)).to_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 expect(env.net_http_class(proxied_host)).to be Net::HTTP end it "should not be marked as a proxy class" do expect(env.net_http_class(proxied_host)).to_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 expect(env.net_http_class(proxied_host)).to be < Net::HTTP end it "should be marked as a proxy class" do expect(env.net_http_class(proxied_host)).to be_proxy_class end end end end end end librarianp-0.6.4/spec/unit/action/0000755000004100000410000000000013172517676017101 5ustar www-datawww-datalibrarianp-0.6.4/spec/unit/action/ensure_spec.rb0000644000004100000410000000152213172517676021741 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 librarianp-0.6.4/spec/unit/action/install_spec.rb0000644000004100000410000000704513172517676022114 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(:destructive?) { true } action.stub_chain(:lock, :manifests) { manifests } end after do action.run end it "should sort and install the manifests" do expect(ManifestSet).to receive(:sort).with(manifests).exactly(:once).ordered { sorted_manifests } install_path.stub(:exist?) { false } expect(install_path).to receive(:mkpath).exactly(:once).ordered sorted_manifests.each do |manifest| expect(manifest).to 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 } expect(install_path).to receive(:rmtree) expect(install_path).to receive(:mkpath) end end end end end end librarianp-0.6.4/spec/unit/action/resolve_spec.rb0000644000004100000410000000302113172517676022113 0ustar www-datawww-datarequire "librarian/error" require "librarian/logger" require "librarian/action/resolve" require "librarian/mock/environment" require "librarian/mock/source" module Librarian describe Action::Resolve do let(:options) { {} } let(:spec) { Spec.new([], dependencies, []) } let(:env) { Librarian::Mock::Environment.new } let(:action) { described_class.new(env, options) } let(:source1) { Librarian::Mock::Source::Mock.new(env, "source1", {}) } let(:source2) { Librarian::Mock::Source::Mock.new(env, "source2", {}) } before do env.stub(:specfile => double(:read => spec)) end describe "#run" do describe "behavior" do describe "merge duplicated dependencies" do let(:options) { {:force => true} } let(:dependency1) { Dependency.new('dependency_name', '1.0.0', source1) } let(:dependency2) { Dependency.new('dependency_name', '1.0.0', source2) } let(:dependencies) { [ dependency1, dependency2 ] } let(:manifest) do m = Manifest.new(source1, dependency1.name) m.version = '1.0.0' m.dependencies = [] m end it "should merge duplicated dependencies" do Dependency.any_instance.stub(:manifests => [manifest]) Librarian::Logger.any_instance.stub(:info) action.stub(:persist_resolution) resolution = action.run expect(resolution.dependencies).to eq([dependency2]) end end end end end end librarianp-0.6.4/spec/unit/action/base_spec.rb0000644000004100000410000000052713172517676021356 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 expect(action.environment).to be env end end end librarianp-0.6.4/spec/unit/action/clean_spec.rb0000644000004100000410000000517513172517676021532 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 expect(env.cache_path).to 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 expect(env.cache_path).to 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 expect(env.install_path).to 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| expect(child).to 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| expect(child).to receive(:rmtree).never end children.reject(&:file?).each do |child| expect(child).to receive(:rmtree).exactly(:once) end end end end end end end end librarianp-0.6.4/spec/unit/lockfile_spec.rb0000644000004100000410000000302513172517676020753 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 { expect(subject.manifests.size).to eq 1 } 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 expect(lockfile_text).to_not be_nil end end context "saving and reloading" do let(:reloaded_resolution) { lockfile.load(lockfile_text) } it "should have the expected manifests" do expect(reloaded_resolution.manifests.count).to eq 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 expect(bounced_lockfile_text).to eq lockfile_text end end end end end librarianp-0.6.4/spec/unit/manifest_set_spec.rb0000644000004100000410000001276313172517676021655 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 expect(set.to_a).to match_array( array ) end it "should give back the hash" do expect(set.to_hash).to eq hash end end context "with a hash" do let(:set) { described_class.new(hash) } it "should give back the array" do expect(set.to_a).to match_array( array ) end it "should give back the hash" do expect(set.to_hash).to eq 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!([]) expect(set.to_a).to match_array( [jelly, butter, jam] ) end it "should remove only the named elements" do set.shallow_strip!(["butter", "jam"]) expect(set.to_a).to match_array( [jelly] ) end it "should allow removing all the elements" do set.shallow_strip!(["jelly", "butter", "jam"]) expect(set.to_a).to match_array( [] ) 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!([]) expect(set.to_a).to match_array( [] ) end it "should keep only the named elements" do set.shallow_keep!(["butter", "jam"]) expect(set.to_a).to match_array( [butter, jam] ) end it "should allow keeping all the elements" do set.shallow_keep!(["jelly", "butter", "jam"]) expect(set.to_a).to match_array( [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!([]) expect(set.to_a).to match_array( [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"]) expect(set.to_a).to match_array( [a, b, d, e, f, g] ) end it "should remove the named elements and all their dependencies" do set.deep_strip!(["b"]) expect(set.to_a).to match_array( [a, e, f, g, h] ) end it "should remove an entire tree of dependencies" do set.deep_strip!(["e"]) expect(set.to_a).to match_array( [a, b, c, d] ) end it "should allow removing all the elements" do set.deep_strip!(["a", "e"]) expect(set.to_a).to match_array( [] ) end it "should fail if index does not contain manifest" do expect { set.deep_strip!(["a", "z"]) }.to raise_error(Librarian::Error, /^Unable to find module z/) 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!([]) expect(set.to_a).to match_array( [] ) end it "should keep just the named elements if they have no dependencies" do set.deep_keep!(["c", "h"]) expect(set.to_a).to match_array( [c, h] ) end it "should keep the named elements and all their dependencies" do set.deep_keep!(["b"]) expect(set.to_a).to match_array( [b, c, d] ) end it "should keep an entire tree of dependencies" do set.deep_keep!(["e"]) expect(set.to_a).to match_array( [e, f, g, h] ) end it "should allow keeping all the elements" do set.deep_keep!(["a", "e"]) expect(set.to_a).to match_array( [a, b, c, d, e, f, g, h] ) end end end end librarianp-0.6.4/spec/unit/environment/0000755000004100000410000000000013172517676020170 5ustar www-datawww-datalibrarianp-0.6.4/spec/unit/environment/runtime_cache_spec.rb0000644000004100000410000000414413172517676024340 0ustar www-datawww-datarequire "librarian/environment/runtime_cache" module Librarian class Environment describe RuntimeCache do let(:rtc) { described_class.new } let(:key) { ["brah", "nick"] } let(:key_x) { ["brah", "phar"] } let(:key_y) { ["rost", "phar"] } def triple(keypair) [rtc.include?(*keypair), rtc.get(*keypair), rtc.memo(*keypair){yield}] end context "originally" do specify { expect(triple(key){9}).to eql([false, nil, 9]) } end context "after put" do before { rtc.put(*key){6} } specify { expect(triple(key){9}).to eql([true, 6, 6]) } specify { expect(triple(key_x){9}).to eql([false, nil, 9]) } specify { expect(triple(key_y){9}).to eql([false, nil, 9]) } end context "after put then delete" do before { rtc.put(*key){6} } before { rtc.delete *key } specify { expect(triple(key){9}).to eql([false, nil, 9]) } specify { expect(triple(key_x){9}).to eql([false, nil, 9]) } specify { expect(triple(key_y){9}).to eql([false, nil, 9]) } end context "after memo" do before { rtc.memo(*key){6} } specify { expect(triple(key){9}).to eql([true, 6, 6]) } specify { expect(triple(key_x){9}).to eql([false, nil, 9]) } specify { expect(triple(key_y){9}).to eql([false, nil, 9]) } end context "after memo then delete" do before { rtc.memo(*key){6} } before { rtc.delete *key } specify { expect(triple(key){9}).to eql([false, nil, 9]) } specify { expect(triple(key_x){9}).to eql([false, nil, 9]) } specify { expect(triple(key_y){9}).to eql([false, nil, 9]) } end context "with keyspace wrapper" do let(:krtc) { rtc.keyspace("brah") } let(:key) { "nick" } let(:key_x) { "phar" } def triple(keypair) [krtc.include?(key), krtc.get(key), krtc.memo(key){yield}] end context "after put" do before { krtc.put(key){6} } specify { expect(triple(key){9}).to eql([true, 6, 6]) } end end end end end librarianp-0.6.4/spec/unit/dsl_spec.rb0000644000004100000410000001357413172517676017757 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 expect(spec.dependencies).to_not be_empty expect(spec.dependencies.first.name).to eq 'dependency-1' expect(spec.dependencies.first.source.name).to eq 'source-1' expect(spec.sources).to be_empty end it "should run with a shortcut source" do spec = env.dsl do dep 'dependency-1', :source => :a end expect(spec.dependencies).to_not be_empty expect(spec.dependencies.first.name).to eq 'dependency-1' expect(spec.dependencies.first.source.name).to eq 'source-a' expect(spec.sources).to 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 expect(spec.dependencies).to_not be_empty expect(spec.dependencies.first.name).to eq 'dependency-1' expect(spec.dependencies.first.source.name).to eq 'source-1' expect(spec.sources).to 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 expect(spec.dependencies).to_not be_empty expect(spec.dependencies.first.name).to eq 'dependency-1' expect(spec.dependencies.first.source.name).to eq 'source-1' expect(spec.sources).to be_empty end it "should run with a default hash source" do spec = env.dsl do source :src => 'source-1' dep 'dependency-1' end expect(spec.dependencies).to_not be_empty expect(spec.dependencies.first.name).to eq 'dependency-1' expect(spec.dependencies.first.source.name).to eq 'source-1' expect(spec.sources).to_not be_empty expect(spec.dependencies.first.source).to eq spec.sources.first end it "should run with a default named source" do spec = env.dsl do src 'source-1' dep 'dependency-1' end expect(spec.dependencies).to_not be_empty expect(spec.dependencies.first.name).to eq 'dependency-1' expect(spec.dependencies.first.source.name).to eq 'source-1' expect(spec.sources).to_not be_empty expect(spec.dependencies.first.source).to eq spec.sources.first end it "should run with a default shortcut source" do spec = env.dsl do source :a dep 'dependency-1' end expect(spec.dependencies).to_not be_empty expect(spec.dependencies.first.name).to eq 'dependency-1' expect(spec.dependencies.first.source.name).to eq 'source-a' expect(spec.sources).to_not be_empty expect(spec.dependencies.first.source).to eq 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 expect(spec.dependencies).to_not be_empty expect(spec.dependencies.first.name).to eq 'dependency-1' expect(spec.dependencies.first.source.name).to eq 'source-b' expect(spec.sources).to 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 expect(spec.dependencies).to_not be_empty expect(spec.dependencies.first.name).to eq 'dependency-1' expect(spec.dependencies.first.source.name).to eq 'source-b' expect(spec.sources).to 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 expect(spec.dependencies).to_not be_empty expect(spec.dependencies.first.name).to eq 'dependency-1' expect(spec.dependencies.first.source.name).to eq 'source-b' expect(spec.sources).to_not be_empty expect(spec.sources.first.name).to eq '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 expect(spec.dependencies).to_not be_empty expect(spec.dependencies.first.name).to eq 'dependency-1' expect(spec.dependencies.first.source.name).to eq 'source-b' expect(spec.sources).to_not be_empty expect(spec.sources.first.name).to eq '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 endlibrarianp-0.6.4/spec/unit/source/0000755000004100000410000000000013172517676017124 5ustar www-datawww-datalibrarianp-0.6.4/spec/unit/source/git_spec.rb0000644000004100000410000000206113172517676021245 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", :I_am_unknown => "megapatches") }. to raise_error Error, "unrecognized options: I_am_unknown" end end context "with invalid options" do it "should raise" do expect { described_class.from_spec_args(env, "some://git/repo.git", {:ref => "megapatches", :branch => "megapatches"}) }. to raise_error Error, "at some://git/repo.git, use only one of ref, branch, tag, or commit" end end end end end end librarianp-0.6.4/spec/unit/mock/0000755000004100000410000000000013172517676016555 5ustar www-datawww-datalibrarianp-0.6.4/spec/unit/mock/environment_spec.rb0000644000004100000410000000106413172517676022461 0ustar www-datawww-datarequire "librarian/mock/environment" module Librarian::Mock describe Environment do let(:env) { described_class.new } describe "#version" do specify { expect(env.version).to eq Librarian::VERSION } end describe "#adapter_module" do specify { expect(env.adapter_module).to eq Librarian::Mock } end describe "#adapter_name" do specify { expect(env.adapter_name).to eq "mock" } end describe "#adapter_version" do specify { expect(env.adapter_version).to eq Librarian::Mock::VERSION } end end end librarianp-0.6.4/spec/unit/mock/source/0000755000004100000410000000000013172517676020055 5ustar www-datawww-datalibrarianp-0.6.4/spec/unit/mock/source/mock_spec.rb0000644000004100000410000000057613172517676022355 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 } it { expect(subject.environment).to_not be_nil } end end end end end librarianp-0.6.4/spec/unit/dependency/0000755000004100000410000000000013172517676017742 5ustar www-datawww-datalibrarianp-0.6.4/spec/unit/dependency/requirement_spec.rb0000644000004100000410000000217113172517676023642 0ustar www-datawww-datarequire "librarian/dependency" describe Librarian::Dependency::Requirement do describe "#inspect" do subject(:requirement) { described_class.new(">= 3.2.1") } specify { expect(requirement.inspect). to eq "#= 3.2.1>" } end it 'should handle nil versions' do described_class.new(nil).to_gem_requirement.should eq(Gem::Requirement.new) end it 'should handle nil versions in arrays' do described_class.new([nil]).to_gem_requirement.should eq(Gem::Requirement.new) end it 'should handle .x versions' do described_class.new('1.x').to_gem_requirement.should eq(Gem::Requirement.new('~> 1.0')) described_class.new('1.0.x').to_gem_requirement.should eq(Gem::Requirement.new('~> 1.0.0')) end it 'should handle version ranges' do described_class.new('>=1.1.0 <2.0.0').to_gem_requirement.should eq(Gem::Requirement.new(['>=1.1.0', '<2.0.0'])) end it 'should print to_s' do described_class.new('1.x').to_s.should eq('~> 1.0') s = described_class.new('>=1.1.0 <2.0.0').to_s s.should include(">= 1.1.0") s.should include("< 2.0.0") end end librarianp-0.6.4/spec/unit/config/0000755000004100000410000000000013172517676017071 5ustar www-datawww-datalibrarianp-0.6.4/spec/unit/config/database_spec.rb0000644000004100000410000001727713172517676022212 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 expect(database.global[key]).to eq value end it "should not have the key in the env" do expect(database.env[key]).to be_nil end it "should not have the key locally" do expect(database.local[key]).to be_nil end it "should have the key generally" do expect(database[key]).to eq 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 expect(database.global[key]).to eq value end it "should not have the key in the env" do expect(database.env[key]).to be_nil end it "should not have the key locally" do expect(database.local[key]).to be_nil end it "should have the key generally" do expect(database[key]).to eq value end it "should persist the key" do data = YAML.load_file(global) expect(data).to eq({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 expect(database.global[key]).to be_nil end it "should not have the key in the env" do expect(database.env[key]).to be_nil end it "should not have the key locally" do expect(database.local[key]).to be_nil end it "should not have the key generally" do expect(database[key]).to be_nil end it "should unpersist the key" do expect(File).to_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 expect(database.global[key]).to be_nil end it "should have the key in the env" do expect(database.env[key]).to eq value end it "should not have the key locally" do expect(database.local[key]).to be_nil end it "should have the key generally" do expect(database[key]).to eq 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 expect(database.global[key]).to be_nil end it "should not have the key in the env" do expect(database.env[key]).to be_nil end it "should have the key locally" do expect(database.local[key]).to eq value end it "should have the key generally" do expect(database[key]).to eq 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 expect(database.global[key]).to be_nil end it "should not have the key in the env" do expect(database.env[key]).to be_nil end it "should have the key locally" do expect(database.local[key]).to eq value end it "should have the key generally" do expect(database[key]).to eq value end it "should persist the key" do data = YAML.load_file(local) expect(data).to eq({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 expect(database.global[key]).to be_nil end it "should not have the key in the env" do expect(database.env[key]).to be_nil end it "should not have the key locally" do expect(database.local[key]).to be_nil end it "should not have the key generally" do expect(database[key]).to be_nil end it "should unpersist the key" do expect(File).to_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 expect(database.project_path).to eq 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 expect(database.project_path).to eq Pathname("/non/sense/path/to") end end end context "specfile_path" do context "by default" do it "should give the default specfile path" do expect(database.specfile_path).to eq 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 expect(database.specfile_path).to eq 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 expect(database.specfile_path).to eq 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 expect(database.specfile_path).to eq Pathname("/tmp/Sillyfile") end end end end librarianp-0.6.4/spec/unit/spec_change_set_spec.rb0000644000004100000410000001036413172517676022301 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) expect(lock).to be_correct spec = env.dsl do src 'source-1' dep 'jam' end changes = described_class.new(env, spec, lock) expect(changes).to_not be_same manifests = ManifestSet.new(changes.analyze).to_hash expect(manifests).to have_key('jam') expect(manifests).to_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) expect(lock).to be_correct spec = env.dsl do src 'source-1' dep 'butter' dep 'jam' end changes = described_class.new(env, spec, lock) expect(changes).to_not be_same manifests = ManifestSet.new(changes.analyze).to_hash expect(manifests).to have_key('jam') expect(manifests).to_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) expect(lock).to be_correct spec = env.dsl do src 'source-1' dep 'butter' dep 'jam', '>= 1.0' end changes = described_class.new(env, spec, lock) expect(changes).to_not be_same manifests = ManifestSet.new(changes.analyze).to_hash expect(manifests).to have_key('butter') expect(manifests).to 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) expect(lock).to be_correct spec = env.dsl do src 'source-1' dep 'butter' dep 'jam', '>= 1.1' end changes = described_class.new(env, spec, lock) expect(changes).to_not be_same manifests = ManifestSet.new(changes.analyze).to_hash expect(manifests).to have_key('butter') expect(manifests).to_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) expect(lock).to be_correct spec = env.dsl do src 'source-1' dep 'butter', :src => 'source-2' end changes = described_class.new(env, spec, lock) expect(changes).to_not be_same manifests = ManifestSet.new(changes.analyze).to_hash expect(manifests).to_not have_key('butter') end end end end librarianp-0.6.4/spec/unit/lockfile/0000755000004100000410000000000013172517676017414 5ustar www-datawww-datalibrarianp-0.6.4/spec/unit/lockfile/parser_spec.rb0000644000004100000410000001052513172517676022252 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 expect(resolution.dependencies).to be_empty end it "should give an empty list of manifests" do expect(resolution.manifests).to 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 expect(resolution.dependencies.size).to eq 1 end it "should give a dependency with the expected name" do dependency = resolution.dependencies.first expect(dependency.name).to eq "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. expect(dependency.requirement.to_s).to eq "!= 1.2.6, ~> 1.1" end it "should give a dependency wth the expected source" do dependency = resolution.dependencies.first source = dependency.source expect(source.name).to eq "source-a" end it "should give a list of one manifest" do expect(resolution.manifests.size).to eq 1 end it "should give a manifest with the expected name" do manifest = resolution.manifests.first expect(manifest.name).to eq "jelly" end it "should give a manifest with the expected version" do manifest = resolution.manifests.first expect(manifest.version.to_s).to eq "1.3.5" end it "should give a manifest with no dependencies" do manifest = resolution.manifests.first expect(manifest.dependencies).to be_empty end it "should give a manifest with the expected source" do manifest = resolution.manifests.first source = manifest.source expect(source.name).to eq "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 expect(manifest_source).to 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 expect(resolution.dependencies.size).to eq 1 end it "should have the expected dependency" do dependency = resolution.dependencies.first expect(dependency.name).to eq "jelly" end it "should give a list of all the manifests" do expect(resolution.manifests.size).to eq 2 end it "should include all the expected manifests" do manifests = ManifestSet.new(resolution.manifests) expect(manifests.to_hash.keys).to match_array( %w(butter jelly) ) end it "should have an internally consistent set of manifests" do manifests = ManifestSet.new(resolution.manifests) expect(manifests).to be_consistent end it "should have an externally consistent set of manifests" do dependencies = resolution.dependencies manifests = ManifestSet.new(resolution.manifests) expect(manifests).to be_in_compliance_with dependencies end end end end librarianp-0.6.4/spec/unit/resolver_spec.rb0000644000004100000410000001634313172517676021033 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 { expect(resolution).to 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 { expect(resolution).to 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 expect(spec.sources.size).to eq 2 end let(:resolution) { resolver.resolve(spec) } specify { expect(resolution).to be_correct } it "should have the manifest from the final source with a matching manifest" do manifest = resolution.manifests.find{|m| m.name == "butter"} expect(manifest.source.name).to eq "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 { expect(resolution).to 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 { expect(resolution).to be_nil } end context "a specfile with cyclic constraints" do before do env.registry :clear => true do source 'source-1' do spec 'butter', '1.0' do dependency 'jam', '2.0' end spec 'jam', '2.0' do dependency 'butter', '1.0' end end end end let(:spec) do env.dsl do src 'source-1' dep 'butter' end end let(:resolution) { resolver.resolve(spec) } context "when cyclic resolutions are forbidden" do let(:resolver) { env.resolver(:cyclic => false) } specify { expect(resolution).to be_nil } end context "when cyclic resolutions are permitted" do let(:resolver) { env.resolver(:cyclic => true) } it "should have all the manifests" do manifest_names = resolution.manifests.map(&:name).sort expect(manifest_names).to be == %w[butter jam] end end 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) expect(first_resolution).to be_correct first_manifests = first_resolution.manifests first_manifests_index = Hash[first_manifests.map{|m| [m.name, m]}] expect(first_manifests_index['butter'].version.to_s).to eq '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) expect(second_resolution).to be_correct second_manifests = second_resolution.manifests second_manifests_index = Hash[second_manifests.map{|m| [m.name, m]}] expect(second_manifests_index['butter'].version.to_s).to eq '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) expect(lock).to be_correct spec = env.dsl do src 'source-1' dep 'butter', :src => 'source-2' end changes = SpecChangeSet.new(env, spec, lock) expect(changes).to_not be_same manifests = ManifestSet.new(changes.analyze).to_hash expect(manifests).to_not have_key('butter') lock = resolver.resolve(spec, changes.analyze) expect(lock).to be_correct expect(lock.manifests.map{|m| m.name}).to include('butter') manifest = lock.manifests.find{|m| m.name == 'butter'} expect(manifest).to_not be_nil expect(manifest.source.name).to eq '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) expect(spec.sources.size).to eq 1 source = spec.sources.first expect(source.name).to eq specfile_path.to_s end end end end librarianp-0.6.4/spec/unit/dependency_spec.rb0000644000004100000410000001313013172517676021277 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 librarianp-0.6.4/spec/support/0000755000004100000410000000000013172517676016361 5ustar www-datawww-datalibrarianp-0.6.4/spec/support/fakefs.rb0000644000004100000410000000164613172517676020154 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 librarianp-0.6.4/spec/support/with_env_macro.rb0000644000004100000410000000047613172517676021721 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 librarianp-0.6.4/spec/support/project_path_macro.rb0000644000004100000410000000042013172517676022545 0ustar www-datawww-datamodule Support module ProjectPathMacro project_path = Pathname.new(__FILE__).expand_path project_path = project_path.dirname until project_path.join("Rakefile").exist? PROJECT_PATH = project_path def project_path PROJECT_PATH end end end librarianp-0.6.4/spec/support/method_patch_macro.rb0000644000004100000410000000115013172517676022523 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 librarianp-0.6.4/spec/functional/0000755000004100000410000000000013172517676017007 5ustar www-datawww-datalibrarianp-0.6.4/spec/functional/cli_spec.rb0000644000004100000410000000103313172517676021112 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 expect(stdout).to eq strip_heredoc(<<-STDOUT) librarian-#{Librarian::VERSION} librarian-mock-#{Librarian::Mock::VERSION} STDOUT end end end end end librarianp-0.6.4/spec/functional/source/0000755000004100000410000000000013172517676020307 5ustar www-datawww-datalibrarianp-0.6.4/spec/functional/source/git_spec.rb0000644000004100000410000001152113172517676022431 0ustar www-datawww-datarequire "fileutils" require "pathname" require "securerandom" require "librarian/error" require "librarian/posix" require "librarian/source/git" require "librarian/source/git/repository" require "librarian/mock/environment" require "support/project_path_macro" describe Librarian::Source::Git do include Support::ProjectPathMacro let(:tmp_path) { project_path + "tmp/spec/functional/source/git" } after { tmp_path.rmtree if tmp_path && tmp_path.exist? } let(:env_project_path) { tmp_path + "project" } def cmd!(command) Librarian::Posix.run! command end def git!(command) cmd!([Librarian::Source::Git::Repository.bin] + command) end def new_env Librarian::Mock::Environment.new(:project_path => env_project_path) end context "when the remote is bad" do let(:remote) { tmp_path.join(SecureRandom.hex(8)).to_s } let(:env) { new_env } 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 context "when the remote has a repo" do let(:remote) { tmp_path.join(SecureRandom.hex(8)).to_s } let(:git_source_path) { Pathname.new(remote) } let(:env) { new_env } let(:source) { described_class.new(env, remote, {}) } 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] FileUtils.touch "butter.txt" git! %W[add butter.txt] git! %W[commit -m #{"Initial Commit"}] end end let(:sha) do Dir.chdir(git_source_path) do git!(%W[rev-parse master]).strip end end context "when caching once" do it "has the expected sha" do expect{source.cache!}.to change{source.sha}.from(nil).to(sha) end it "records the history" do expect{source.cache!}.to change{source.git_ops_count}.from(0).to(9) end end context "when caching twice" do before { source.cache! } it "keeps the expected sha" do expect{source.cache!}.to_not change{source.sha} end it "runs git commands once" do expect{source.cache!}.to_not change{source.git_ops_count} end end context "when caching twice from different sources" do let(:other_source) { described_class.new(env, remote, {}) } before { other_source.cache! } it "has the expected sha" do expect{source.cache!}.to change{source.sha}.from(nil).to(sha) end it "records the history" do expect{source.cache!}.to change{source.git_ops_count}.from(0).to(1) end end context "when caching twice from different sources, second time with sha" do let(:other_source) { described_class.new(env, remote, {}) } before { other_source.cache! } let(:source) { described_class.new(env, remote, {:sha => sha}) } it "has the expected sha" do expect{source.cache!}.to_not change{source.sha} end it "records the history" do expect{source.cache!}.to change{source.git_ops_count}.from(0).to(1) end end context "when caching twice from different environments" do let(:other_source) { described_class.new(new_env, remote, {}) } before { other_source.cache! } it "has the expected sha" do expect{source.cache!}.to change{source.sha}.from(nil).to(sha) end it "records the history" do expect{source.cache!}.to change{source.git_ops_count}.from(0).to(8) end end context "when caching twice from different environments, second time with sha" do let(:other_source) { described_class.new(new_env, remote, {}) } before { other_source.cache! } let(:source) { described_class.new(env, remote, {:sha => sha}) } it "has the expected sha" do expect{source.cache!}.to_not change{source.sha} end it "records the history" do expect{source.cache!}.to change{source.git_ops_count}.from(0).to(3) end end context "when the sha is missing from a cached repo" do let(:other_source) { described_class.new(new_env, remote, {}) } before { other_source.cache! } before do Dir.chdir(git_source_path) do FileUtils.touch "jam.txt" git! %w[add jam.txt] git! %W[commit -m #{"Some Jam"}] end end let(:source) { described_class.new(env, remote, {:sha => sha}) } it "has a new remote sha" do expect(sha).to_not eq(other_source.sha) end it "has the expected sha" do expect{source.cache!}.to_not change{source.sha} end it "records the history" do expect{source.cache!}.to change{source.git_ops_count}.from(0).to(8) end end end end librarianp-0.6.4/spec/functional/source/git/0000755000004100000410000000000013172517676021072 5ustar www-datawww-datalibrarianp-0.6.4/spec/functional/source/git/repository_spec.rb0000644000004100000410000001251613172517676024655 0ustar www-datawww-datarequire "fileutils" require "pathname" require "securerandom" require "librarian/posix" require "librarian/source/git/repository" require "librarian/mock/environment" require "support/project_path_macro" describe Librarian::Source::Git::Repository do include Support::ProjectPathMacro let(:env) { Librarian::Mock::Environment.new } 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 { expect(described_class.bin).to_not be_empty } end describe ".git_version" do specify { expect(described_class.git_version).to match( /^\d+(\.\d+)+$/ ) } end context "the original" do subject { described_class.new(env, git_source_path) } it "should recognize it" do expect(subject).to be_git end it "should not list any remotes for it" do expect(subject.remote_names).to be_empty end it "should not list any remote branches for it" do expect(subject.remote_branch_names).to 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)} expect(shas.map(&:class).uniq).to eq [String] expect(shas.map(&:size).uniq).to eq [40] expect(shas.uniq).to eq 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 expect(subject).to be_git end it "should have a single remote for it" do expect(subject.remote_names.size).to eq 1 end it "should have a remote with the expected name" do expect(subject.remote_names.first).to eq "origin" end it "should have the remote branch" do expect(subject.remote_branch_names["origin"]).to include branch end it "should be checked out on the master" do expect(subject).to be_checked_out(master_sha) end context "checking for commits" do it "has the master commit" do expect(subject).to have_commit(master_sha) end it "has the branch commit" do expect(subject).to have_commit(branch_sha) end it "has the tag commit" do expect(subject).to have_commit(tag_sha) end it "has the atag commit" do expect(subject).to have_commit(atag_sha) end it "does not have a made-up commit" do expect(subject).to_not have_commit(SecureRandom.hex(20)) end it "does not have a tree commit" do master_tree_sha = Dir.chdir(git_source_path) do git!(%W[log -1 --no-color --format=tformat:%T master]).strip end expect(master_tree_sha).to match(/\A[0-9a-f]{40}\z/) # sanity expect(subject).to_not have_commit(master_tree_sha) end end context "checking out the branch" do before do subject.checkout! branch end it "should be checked out on the branch" do expect(subject).to be_checked_out(branch_sha) end it "should not be checked out on the master" do expect(subject).to_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 expect(subject).to be_checked_out(tag_sha) end it "should not be checked out on the master" do expect(subject).to_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 expect(subject).to be_checked_out(atag_sha) end it "should not be checked out on the master" do expect(subject).to_not be_checked_out(master_sha) end end end end librarianp-0.6.4/spec/functional/posix_spec.rb0000644000004100000410000000144213172517676021511 0ustar www-datawww-datarequire "librarian/posix" require "support/project_path_macro" describe Librarian::Posix do include Support::ProjectPathMacro 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 expect(res).to eq "hello there" end it "changes directory" do tmp_path.mkpath res = described_class.run!(%w[pwd], :chdir => tmp_path).strip expect(res).to eq 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 expect(line).to eq "KOALA=BEAR" end end end librarianp-0.6.4/.travis.yml0000644000004100000410000000056113172517676016026 0ustar www-datawww-datasudo: false language: ruby script: bundle exec rake rvm: - '2.3.5' - '2.2.8' - '2.1.9' - '2.0' deploy: provider: rubygems api_key: secure: "HkdoYaLek2mCT+e7pW0pHMLiIuikUMihfqzspltzmN673HBvMCMZqk4sIm4b3Zi+RMtNTb4dnMxxhehKyyzF88UIRHqV9OUPT3LQLE/BgHThxLD1+CSbVS1SZKY7UfTIcqW3/otORBCI5YAXiycEnRFIe2R/S3n0c/gD9dBcobg=" gem: librarianp on: tags: true librarianp-0.6.4/lib/0000755000004100000410000000000013172517676014461 5ustar www-datawww-datalibrarianp-0.6.4/lib/librarian.rb0000644000004100000410000000022613172517676016751 0ustar www-datawww-datarequire 'librarian/version' require 'librarian/environment' module Librarian extend self def environment_class self::Environment end end librarianp-0.6.4/lib/librarian/0000755000004100000410000000000013172517676016424 5ustar www-datawww-datalibrarianp-0.6.4/lib/librarian/source.rb0000644000004100000410000000007713172517676020255 0ustar www-datawww-datarequire 'librarian/source/git' require 'librarian/source/path' librarianp-0.6.4/lib/librarian/manifest/0000755000004100000410000000000013172517676020232 5ustar www-datawww-datalibrarianp-0.6.4/lib/librarian/manifest/pre_release_version.rb0000644000004100000410000000345213172517676024616 0ustar www-datawww-datamodule Librarian class Manifest class PreReleaseVersion # Compares pre-release component ids using Semver 2.0.0 spec def self.compare_components(this_id,other_id) case # Strings have higher precedence than numbers when (this_id.is_a?(Integer) and other_id.is_a?(String)) -1 when (this_id.is_a?(String) and other_id.is_a?(Integer)) 1 else this_id <=> other_id end end # Parses pre-release components `a.b.c` into an array ``[a,b,c]` # Converts numeric components into +Integer+ def self.parse(prerelease) if prerelease.nil? [] else prerelease.split('.').collect do |id| id = Integer(id) if /^[0-9]+$/ =~ id id end end end include Comparable attr_reader :components def initialize(prerelease) @prerelease = prerelease @components = PreReleaseVersion.parse(prerelease) end def to_s @prerelease end def <=>(other) # null-fill zip array to prevent loss of components z = Array.new([components.length,other.components.length]) # Compare each component against the other comp = z.zip(components,other.components).collect do |ids| case # All components being equal, the version with more of them takes precedence when ids[1].nil? # Self has less elements, other wins -1 when ids[2].nil? # Other has less elements, self wins 1 else PreReleaseVersion.compare_components(ids[1],ids[2]) end end # Chose the first non-zero comparison or return 0 comp.delete_if {|c| c == 0}[0] || 0 end end end end librarianp-0.6.4/lib/librarian/manifest/version.rb0000644000004100000410000000366513172517676022256 0ustar www-datawww-datamodule Librarian class Manifest class Version include Comparable @@SEMANTIC_VERSION_PATTERN = /^([0-9]+\.[0-9]+(?:\.[0-9]+)?)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/ attr_reader :prerelease def initialize(*args) args = initialize_normalize_args(args) semver = Version.parse_semver(*args) if semver self.backing = Gem::Version.new(semver[:version]) @prerelease = semver[:prerelease] @full_version = semver[:full_version] else self.backing = Gem::Version.new(*args) @full_version = to_gem_version.to_s end end def to_gem_version backing end def <=>(other) cmp = to_gem_version <=> other.to_gem_version # Should compare pre-release versions? if cmp == 0 and not (prerelease.nil? and other.prerelease.nil?) case # Versions without prerelease take precedence when (prerelease.nil? and not other.prerelease.nil?) 1 when (not prerelease.nil? and other.prerelease.nil?) -1 else prerelease <=> other.prerelease end else cmp end end def to_s @full_version end def inspect "#<#{self.class} #{to_s}>" end def self.parse_semver(version_string) parsed = @@SEMANTIC_VERSION_PATTERN.match(version_string.strip) if parsed { :full_version => parsed[0], :version => parsed[1], :prerelease => (PreReleaseVersion.new(parsed[2]) if parsed[2]), :build => parsed[3] } end end private def initialize_normalize_args(args) args.map do |arg| arg = [arg] if self.class === arg arg end end attr_accessor :backing end end end librarianp-0.6.4/lib/librarian/manifest.rb0000644000004100000410000000546413172517676020570 0ustar www-datawww-datarequire 'rubygems' require 'librarian/manifest/version' require 'librarian/manifest/pre_release_version' module Librarian class Manifest 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 # Remove dependencies excluded, and return them def exclude_dependencies!(exclusions) included, excluded = dependencies.partition { |d| !exclusions.include? d.name } self.dependencies = included excluded 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! dependencies, duplicated = Dependency.remove_duplicate_dependencies(source.fetch_dependencies(name, version, extra)) duplicated.each do |name, dependencies_same_name| environment.logger.info { "Dependency '#{name}' duplicated for module #{self.name}, merging: #{dependencies_same_name.map{|d| d.to_s}}" } end dependencies 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) name =~ /\A\S(?:.*\S)?\z/ and return raise ArgumentError, "name (#{name.inspect}) must be sensible" end end end librarianp-0.6.4/lib/librarian/action/0000755000004100000410000000000013172517676017701 5ustar www-datawww-datalibrarianp-0.6.4/lib/librarian/action/update.rb0000644000004100000410000000232213172517676021507 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 librarianp-0.6.4/lib/librarian/action/persist_resolution_mixin.rb0000644000004100000410000000226213172517676025410 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 librarianp-0.6.4/lib/librarian/action/clean.rb0000644000004100000410000000152213172517676021310 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 librarianp-0.6.4/lib/librarian/action/install.rb0000644000004100000410000000367413172517676021706 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? && destructive? 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 destructive? environment.config_db.local['destructive'] == 'true' 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 librarianp-0.6.4/lib/librarian/action/ensure.rb0000644000004100000410000000057613172517676021537 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 librarianp-0.6.4/lib/librarian/action/base.rb0000644000004100000410000000064013172517676021140 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 librarianp-0.6.4/lib/librarian/action/resolve.rb0000644000004100000410000000250613172517676021710 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 spec.dependencies, duplicated = Dependency.remove_duplicate_dependencies(spec.dependencies) duplicated.each do |name, dependencies_same_name| environment.logger.info { "Dependency '#{name}' duplicated for module, merging: #{dependencies_same_name.map{|d| d.to_s}}" } end resolution = resolver.resolve(spec, manifests) persist_resolution(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 librarianp-0.6.4/lib/librarian/specfile.rb0000644000004100000410000000054413172517676020546 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 = []) @spec ||= environment.dsl(path, precache_sources) end end end librarianp-0.6.4/lib/librarian/dependency.rb0000644000004100000410000001342113172517676021070 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 alias :eql? :== def hash self.to_s.hash end def to_s to_gem_requirement.to_s end def inspect "#<#{self.class} #{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 case arg when nil nil when Array arg.map { |item| parse(item) } when String parse(arg) else # Gem::Requirement, convert to string (ie. =1.0) so we can concat later # Gem::Requirements can not be concatenated arg.requirements.map{|x,y| "#{x}#{y}"} end end.flatten end # build an array if the argument is a string defining a range # or a ~> 1.0 type version if string is 1.x def parse(arg) return nil if arg.nil? match = range_requirement(arg) return [match[1], match[2]] if match match = pessimistic_requirement(arg) return "~> #{match[1]}.0" if match arg 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 # A version range: >=1.0 <2.0 def range_requirement(arg) arg.match(/(>=? ?\d+(?:\.\d+){0,2}) (<=? ?\d+(?:\.\d+){0,2})/) end # A string with .x: 1.x, 2.1.x def pessimistic_requirement(arg) arg.match(/(\d+(?:\.\d+)?)\.x/) 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 alias :eql? :== def hash self.to_s.hash end def consistent_with?(other) name != other.name || requirement.consistent_with?(other.requirement) end def inconsistent_with?(other) !consistent_with?(other) end class << self # merge dependencies with the same name into one # with the source of the first one and merged requirements def merge_dependencies(dependencies) requirement = Dependency::Requirement.new(*dependencies.map{|d| d.requirement}) dependencies.last.class.new(dependencies.last.name, requirement, dependencies.last.source) end # Avoid duplicated dependencies with different sources or requirements # Return [merged dependnecies, duplicates as a map by name] def remove_duplicate_dependencies(dependencies) uniq = [] duplicated = {} dependencies_by_name = dependencies.group_by{|d| d.name} dependencies_by_name.map do |name, dependencies_same_name| if dependencies_same_name.size > 1 duplicated[name] = dependencies_same_name uniq << merge_dependencies(dependencies_same_name) else uniq << dependencies_same_name.first end end [uniq, duplicated] end end private def assert_name_valid!(name) name =~ /\A\S(?:.*\S)?\z/ and return raise ArgumentError, "name (#{name.inspect}) must be sensible" end end end librarianp-0.6.4/lib/librarian/posix.rb0000644000004100000410000000672413172517676020124 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.9 in mind def rescuing(*klasses) begin yield rescue *klasses end end 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]]}] out, err, wait = nil, nil, nil begin ENV.update env Dir.chdir(chdir || Dir.pwd) do IO.popen3(*command) do |i, o, e, w| rescuing(Errno::EBADF){ i.close } # jruby/1.9 can raise EBADF out, err, wait = o.read, e.read, w end end ensure ENV.update old_env end s = wait ? wait.value : $? # wait is 1.9+-only s.success? or CommandFailure.raise! command, s, 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 librarianp-0.6.4/lib/librarian/resolver.rb0000644000004100000410000000521313172517676020613 0ustar www-datawww-datarequire 'librarian/error' require 'librarian/resolver/implementation' require 'librarian/manifest_set' require 'librarian/resolution' module Librarian class Resolver attr_accessor :environment, :cyclic private :environment=, :cyclic= # Options: # cyclic: truthy if the resolver should permit cyclic resolutions def initialize(environment, options = { }) unrecognized_options = options.keys - [:cyclic] unrecognized_options.empty? or raise Error, "unrecognized options: #{unrecognized_options.join(", ")}" self.environment = environment self.cyclic = !!options[:cyclic] end def resolve(spec, partial_manifests = []) manifests = implementation(spec).resolve(partial_manifests) manifests or return enforce_consistency!(spec.dependencies, manifests) enforce_acyclicity!(manifests) unless cyclic manifests = sort(manifests) Resolution.new(spec.dependencies, manifests) end private def implementation(spec) Implementation.new(self, spec, :cyclic => cyclic) 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 enforce_acyclicity!(manifests) ManifestSet.cyclic?(manifests) or return debug { "Resolver Malfunctioned!" } raise Error, "Resolver Malfunctioned!" end def sort(manifests) ManifestSet.sort(manifests) end def debug(*args, &block) environment.logger.debug(*args, &block) end end end librarianp-0.6.4/lib/librarian/manifest_set.rb0000644000004100000410000000711213172517676021433 0ustar www-datawww-datarequire "librarian/algorithms" module Librarian class ManifestSet 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 cyclic?(manifests) manifests = Hash[manifests.map{|m| [m.name, m]}] if Array === manifests manifest_pairs = Hash[manifests.map{|k, m| [k, m.dependencies.map{|d| d.name}]}] adj_algs.cyclic?(manifest_pairs) end def sort(manifests) manifests = Hash[manifests.map{|m| [m.name, m]}] if Array === manifests manifest_pairs = Hash[manifests.map{|k, m| [k, m.dependencies.map{|d| d.name}]}] manifest_names = adj_algs.tsort_cyclic(manifest_pairs) manifest_names.map{|n| manifests[n]} end private def method_for(manifests) case manifests when Hash :to_hash when Array :to_a end end def adj_algs Algorithms::AdjacencyListDirectedGraph 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 raise(Error, "Unable to find module #{name}. The dependency descriptor may be out of sync with the lock, try running 'install' first") if index[name].nil? 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 librarianp-0.6.4/lib/librarian/linter/0000755000004100000410000000000013172517676017721 5ustar www-datawww-datalibrarianp-0.6.4/lib/librarian/linter/source_linter.rb0000644000004100000410000000214413172517676023124 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 librarianp-0.6.4/lib/librarian/logger.rb0000644000004100000410000000221013172517676020223 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 warn(string = nil, &block) return unless ui ui.warn(string || yield) 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 librarianp-0.6.4/lib/librarian/spec.rb0000644000004100000410000000041413172517676017702 0ustar www-datawww-datamodule Librarian class Spec attr_accessor :sources, :dependencies, :exclusions def initialize(sources, dependencies, exclusions = []) self.sources = sources self.dependencies = dependencies self.exclusions = exclusions end end end librarianp-0.6.4/lib/librarian/environment/0000755000004100000410000000000013172517676020770 5ustar www-datawww-datalibrarianp-0.6.4/lib/librarian/environment/runtime_cache.rb0000644000004100000410000000442413172517676024127 0ustar www-datawww-datarequire "librarian/error" module Librarian class Environment class RuntimeCache class KeyspaceCache class << self private def delegate_to_backing_cache(*methods) methods.each do |method| define_method "#{method}" do |*args, &block| # TODO: When we drop ruby-1.8.7 support, use #public_send. runtime_cache.send(method, keyspace, *args, &block) end end end end attr_reader :runtime_cache, :keyspace def initialize(runtime_cache, keyspace) self.runtime_cache = runtime_cache self.keyspace = keyspace end delegate_to_backing_cache *[ :include?, :get, :put, :delete, :memo, :once, :[], :[]=, ] private attr_writer :runtime_cache, :keyspace end def initialize self.data = {} end def include?(keyspace, key) data.include?(combined_key(keyspace, key)) end def get(keyspace, key) data[combined_key(keyspace, key)] end def put(keyspace, key, value = nil) data[combined_key(keyspace, key)] = block_given? ? yield : value end def delete(keyspace, key) data.delete(combined_key(keyspace, key)) end def memo(keyspace, key) put(keyspace, key, yield) unless include?(keyspace, key) get(keyspace, key) end def once(keyspace, key) memo(keyspace, key) { yield ; nil } end def [](keyspace, key) get(keyspace, key) end def []=(keyspace, key, value) put(keyspace, key, value) end def keyspace(keyspace) KeyspaceCache.new(self, keyspace) end private attr_accessor :data def combined_key(keyspace, key) keyspace.kind_of?(String) or raise Error, "keyspace must be a string" keyspace.size > 0 or raise Error, "keyspace must not be empty" keyspace.size < 2**16 or raise Error, "keyspace must not be too large" key.kind_of?(String) or raise Error, "key must be a string" [keyspace.size.to_s(16).rjust(4, "0"), keyspace, key].join end end end end librarianp-0.6.4/lib/librarian/mock.rb0000644000004100000410000000004313172517676017677 0ustar www-datawww-datarequire 'librarian/mock/extension' librarianp-0.6.4/lib/librarian/cli.rb0000644000004100000410000001476513172517676017535 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 librarianp-0.6.4/lib/librarian/helpers.rb0000644000004100000410000000132113172517676020410 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 librarianp-0.6.4/lib/librarian/version.rb0000644000004100000410000000014113172517676020432 0ustar www-datawww-datamodule Librarian VERSION = File.read(File.expand_path("../../../VERSION", __FILE__)).strip end librarianp-0.6.4/lib/librarian/source/0000755000004100000410000000000013172517676017724 5ustar www-datawww-datalibrarianp-0.6.4/lib/librarian/source/git.rb0000644000004100000410000001155413172517676021042 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, :branch, :tag, :commit, :path] DEFAULTS = { :ref => 'master' } attr_accessor :environment private :environment= attr_accessor :uri, :ref, :branch, :tag, :commit, :sha, :path private :uri=, :ref=, :branch=, :tag=, :commit=, :sha=, :path= def initialize(environment, uri, options) validate_options(uri, options) self.environment = environment self.uri = uri self.ref = options[:ref] || options[:branch] || options[:tag] || options[:commit] || 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 alias :eql? :== def hash self.to_s.hash 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 # Probably unnecessary: nobody should be writing to our cache but us. # Just a precaution. repository_clean_once! unless sha repository_update_once! self.sha = fetch_sha_memo end unless repository.checked_out?(sha) repository_update_once! unless repository.has_commit?(sha) repository.checkout!(sha) # Probably unnecessary: if git fails to checkout, it should exit # nonzero, and we should expect Librarian::Posix::CommandFailure. raise Error, "failed to checkout #{sha}" unless repository.checked_out?(sha) end end # For tests def git_ops_count repository.git_ops_history.size 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 + "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 repository_clean_once! remote = repository.default_remote runtime_cache.once ['repository-clean', uri, ref].to_s do repository.reset_hard! repository.clean! end end def repository_update_once! remote = repository.default_remote runtime_cache.once ['repository-update', uri, remote, ref].to_s do repository.fetch! remote repository.fetch! remote, :tags => true end end def fetch_sha_memo remote = repository.default_remote runtime_cache.memo ['fetch-sha', uri, remote, ref].to_s do repository.hash_from(remote, ref) end end def cache_key @cache_key ||= begin uri_part = uri ref_part = "##{ref}" key_source = [uri_part, ref_part].join Digest::MD5.hexdigest(key_source)[0..15] end end def runtime_cache @runtime_cache ||= environment.runtime_cache.keyspace(self.class.name) end def validate_options(uri, options) found = 0; [:ref, :branch, :tag, :commit].each do |opt| found += 1 if options.has_key?(opt) found > 1 and raise Error, "at #{uri}, use only one of ref, branch, tag, or commit" end end end end end librarianp-0.6.4/lib/librarian/source/path.rb0000644000004100000410000000175713172517676021217 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, :path private :environment=, :path= def initialize(environment, path, options) self.environment = environment self.path = path end def to_s path.to_s end def ==(other) other && self.class == other.class && self.path == other.path end alias :eql? :== def hash self.to_s.hash 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 librarianp-0.6.4/lib/librarian/source/local.rb0000644000004100000410000000233213172517676021343 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 librarianp-0.6.4/lib/librarian/source/git/0000755000004100000410000000000013172517676020507 5ustar www-datawww-datalibrarianp-0.6.4/lib/librarian/source/git/repository.rb0000644000004100000410000001166413172517676023263 0ustar www-datawww-datarequire "pathname" require "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] Posix.run!(command).strip =~ /\Agit version (\d+(\.\d+)*)/ && $1 end end attr_accessor :environment, :path, :git_ops_history private :environment=, :path=, :git_ops_history= def initialize(environment, path) self.environment = environment self.path = Pathname.new(path) self.git_ops_history = [] 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 has_commit?(sha) command = %W(log -1 --no-color --format=tformat:%H #{sha}) run!(command, :chdir => true).strip == sha rescue Posix::CommandFailure => e false 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-parse #{reference}^{commit} --quiet) 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 git_ops_history << command + [{:pwd => pwd}] unless silent if out.size > 0 out.lines.each do |line| debug { " --> #{line}" } end else debug { " --- No output" } end end out rescue Posix::CommandFailure => e git_ops_history << command + [{:pwd => pwd}] status, stderr = e.status, e.message unless silent debug { " --- Exited with #{status}" } if stderr.size > 0 stderr.lines.each do |line| debug { " --> #{line}" } end else debug { " --- No output" } end end raise e 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 librarianp-0.6.4/lib/librarian/source/basic_api.rb0000644000004100000410000000217213172517676022165 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 librarianp-0.6.4/lib/librarian/mock/0000755000004100000410000000000013172517676017355 5ustar www-datawww-datalibrarianp-0.6.4/lib/librarian/mock/source.rb0000644000004100000410000000004513172517676021201 0ustar www-datawww-datarequire 'librarian/mock/source/mock' librarianp-0.6.4/lib/librarian/mock/cli.rb0000644000004100000410000000042413172517676020451 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 librarianp-0.6.4/lib/librarian/mock/version.rb0000644000004100000410000000007713172517676021373 0ustar www-datawww-datamodule Librarian module Mock VERSION = "0.1.2" end end librarianp-0.6.4/lib/librarian/mock/source/0000755000004100000410000000000013172517676020655 5ustar www-datawww-datalibrarianp-0.6.4/lib/librarian/mock/source/mock.rb0000644000004100000410000000325413172517676022137 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 alias :eql? :== def hash self.to_s.hash 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 librarianp-0.6.4/lib/librarian/mock/source/mock/0000755000004100000410000000000013172517676021606 5ustar www-datawww-datalibrarianp-0.6.4/lib/librarian/mock/source/mock/registry.rb0000644000004100000410000000416113172517676024005 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 librarianp-0.6.4/lib/librarian/mock/environment.rb0000644000004100000410000000071613172517676022252 0ustar www-datawww-datarequire "librarian/environment" require "librarian/ui" require "librarian/mock/dsl" require "librarian/mock/version" require 'thor' module Librarian module Mock class Environment < Environment def ui Librarian::UI::Shell.new(Thor::Shell::Basic.new) end def registry(options = nil, &block) @registry ||= Source::Mock::Registry.new @registry.merge!(options, &block) @registry end end end end librarianp-0.6.4/lib/librarian/mock/extension.rb0000644000004100000410000000016513172517676021720 0ustar www-datawww-datarequire 'librarian/mock/environment' module Librarian module Mock extend self extend Librarian end end librarianp-0.6.4/lib/librarian/mock/dsl.rb0000644000004100000410000000035213172517676020464 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 librarianp-0.6.4/lib/librarian/config.rb0000644000004100000410000000012113172517676020210 0ustar www-datawww-datarequire "librarian/config/database" module Librarian module Config end end librarianp-0.6.4/lib/librarian/resolution.rb0000644000004100000410000000267713172517676021170 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 librarianp-0.6.4/lib/librarian/config/0000755000004100000410000000000013172517676017671 5ustar www-datawww-datalibrarianp-0.6.4/lib/librarian/config/source.rb0000644000004100000410000000714713172517676021527 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 librarianp-0.6.4/lib/librarian/config/database.rb0000644000004100000410000001177713172517676021777 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 librarianp-0.6.4/lib/librarian/config/hash_source.rb0000644000004100000410000000112613172517676022521 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 librarianp-0.6.4/lib/librarian/config/file_source.rb0000644000004100000410000000176313172517676022524 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 librarianp-0.6.4/lib/librarian/dsl/0000755000004100000410000000000013172517676017206 5ustar www-datawww-datalibrarianp-0.6.4/lib/librarian/dsl/receiver.rb0000644000004100000410000000234313172517676021341 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(:exclusion) do |*args, &block| target.exclusion(*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 librarianp-0.6.4/lib/librarian/dsl/target.rb0000644000004100000410000001176613172517676021034 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 = [] @exclusions = [] 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, @exclusions) 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 exclusion(name) @exclusions << name 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 librarianp-0.6.4/lib/librarian/spec_change_set.rb0000644000004100000410000001374113172517676022071 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 librarianp-0.6.4/lib/librarian/environment.rb0000644000004100000410000001137213172517676021321 0ustar www-datawww-datarequire "pathname" require 'net/http' require "uri" require "etc" require "librarian/helpers" 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" require "librarian/environment/runtime_cache" module Librarian class Environment include Support::AbstractMethod attr_accessor :ui attr_reader :runtime_cache 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] @runtime_cache = RuntimeCache.new 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 ||= 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(options = { }) Resolver.new(self, resolver_options.merge(options)) end def resolver_options { :cyclic => resolver_permit_cyclic_reslutions?, } end def resolver_permit_cyclic_reslutions? false 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 def inspect "#<#{self.class}:0x#{__id__.to_s(16)}>" 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 librarianp-0.6.4/lib/librarian/lockfile.rb0000644000004100000410000000077313172517676020550 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 librarianp-0.6.4/lib/librarian/ui.rb0000644000004100000410000000217313172517676017371 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 endlibrarianp-0.6.4/lib/librarian/rspec/0000755000004100000410000000000013172517676017540 5ustar www-datawww-datalibrarianp-0.6.4/lib/librarian/rspec/support/0000755000004100000410000000000013172517676021254 5ustar www-datawww-datalibrarianp-0.6.4/lib/librarian/rspec/support/cli_macro.rb0000644000004100000410000000563113172517676023536 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 librarianp-0.6.4/lib/librarian/action.rb0000644000004100000410000000025313172517676020226 0ustar www-datawww-datarequire "librarian/action/clean" require "librarian/action/ensure" require "librarian/action/install" require "librarian/action/resolve" require "librarian/action/update" librarianp-0.6.4/lib/librarian/cli/0000755000004100000410000000000013172517676017173 5ustar www-datawww-datalibrarianp-0.6.4/lib/librarian/cli/manifest_presenter.rb0000644000004100000410000000431013172517676023413 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 scope { yield } if block_given? 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 librarianp-0.6.4/lib/librarian/algorithms.rb0000644000004100000410000000735513172517676021134 0ustar www-datawww-datarequire "set" require "tsort" module Librarian module Algorithms class GraphHash < Hash include TSort def tsort_each_node(&block) keys.sort.each(&block) # demand determinism end def tsort_each_child(node, &block) children = self[node] children && children.sort.each(&block) # demand determinism end class << self def from(hash) o = new hash.each{|k, v| o[k] = v} o end end end module AdjacencyListDirectedGraph extend self def cyclic?(graph) each_cyclic_strongly_connected_component_set(graph).any? end # Topological sort of the graph with an approximately minimal feedback arc # set removed. def tsort_cyclic(graph) fag = feedback_arc_graph(graph) reduced_graph = subtract_edges_graph(graph, fag) GraphHash.from(reduced_graph).tsort end # Returns an approximately minimal feedback arc set, lifted into a graph. def feedback_arc_graph(graph) edges_to_graph(feedback_arc_set(graph)) end # Returns an approximately minimal feedback arc set. def feedback_arc_set(graph) fas = feedback_arc_set_step0(graph) feedback_arc_set_step1(graph, fas) end private def edges_to_graph(edges) graph = {} edges.each do |(u, v)| graph[u] ||= [] graph[u] << v graph[v] ||= nil end graph end def subtract_edges_graph(graph, edges_graph) xgraph = {} graph.each do |n, vs| dests = edges_graph[n] xgraph[n] = !vs ? vs : !dests ? vs : vs - dests end xgraph end def each_cyclic_strongly_connected_component_set(graph) return enum_for(__method__, graph) unless block_given? GraphHash.from(graph).each_strongly_connected_component do |scc| if scc.size == 1 n = scc.first vs = graph[n] or next vs.include?(n) or next end yield scc end end # Partitions the graph into its strongly connected component subgraphs, # removes the acyclic single-vertex components (multi-vertex components # are guaranteed to be cyclic), and yields each cyclic strongly connected # component. def each_cyclic_strongly_connected_component_graph(graph) return enum_for(__method__, graph) unless block_given? each_cyclic_strongly_connected_component_set(graph) do |scc| sccs = scc.to_set sccg = GraphHash.new scc.each do |n| vs = graph[n] sccg[n] = vs && vs.select{|v| sccs.include?(v)} end yield sccg end end # The 0th step of computing a feedback arc set: pick out vertices whose # removals will make the graph acyclic. def feedback_arc_set_step0(graph) fas = [] each_cyclic_strongly_connected_component_graph(graph) do |scc| scc.keys.sort.each do |n| # demand determinism vs = scc[n] or next vs.size > 0 or next vs.sort! # demand determinism fas << [n, vs.shift] break end end fas end # The 1st step of computing a feedback arc set: pick out vertices from the # 0th step whose removals turn out to be unnecessary. def feedback_arc_set_step1(graph, fas) reduced_graph = subtract_edges_graph(graph, edges_to_graph(fas)) fas.select do |(u, v)| reduced_graph[u] ||= [] reduced_graph[u] << v cyclic = cyclic?(reduced_graph) reduced_graph[u].pop if cyclic cyclic end end end end end librarianp-0.6.4/lib/librarian/lockfile/0000755000004100000410000000000013172517676020214 5ustar www-datawww-datalibrarianp-0.6.4/lib/librarian/lockfile/parser.rb0000644000004100000410000000762513172517676022047 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 << environment.dsl_class.dependency_type.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| environment.dsl_class.dependency_type.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| environment.dsl_class.dependency_type.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 librarianp-0.6.4/lib/librarian/lockfile/compiler.rb0000644000004100000410000000354713172517676022364 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 librarianp-0.6.4/lib/librarian/support/0000755000004100000410000000000013172517676020140 5ustar www-datawww-datalibrarianp-0.6.4/lib/librarian/support/abstract_method.rb0000644000004100000410000000071013172517676023626 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 librarianp-0.6.4/lib/librarian/error.rb0000644000004100000410000000007113172517676020100 0ustar www-datawww-datamodule Librarian class Error < StandardError end end librarianp-0.6.4/lib/librarian/dsl.rb0000644000004100000410000000567513172517676017550 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 default_specfile nil end def post_process_target(target) nil end def receiver(target) Receiver.new(target) 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? if specfile.kind_of?(Pathname) and !File.exists?(specfile) debug { "Specfile #{specfile} not found, using defaults" } unless specfile.nil? receiver(target).run(specfile, &default_specfile) else receiver(target).run(specfile) end post_process_target(target) 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 librarianp-0.6.4/lib/librarian/resolver/0000755000004100000410000000000013172517676020265 5ustar www-datawww-datalibrarianp-0.6.4/lib/librarian/resolver/implementation.rb0000644000004100000410000001651713172517676023651 0ustar www-datawww-datarequire 'set' require 'librarian/algorithms' require '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 class State attr_accessor :manifests, :dependencies, :queue private :manifests=, :dependencies=, :queue= def initialize(manifests, dependencies, queue) self.manifests = manifests self.dependencies = dependencies # resolved self.queue = queue # scheduled end end attr_accessor :resolver, :spec, :cyclic private :resolver=, :spec=, :cyclic= def initialize(resolver, spec, options = { }) unrecognized_options = options.keys - [:cyclic] unrecognized_options.empty? or raise Error, "unrecognized options: #{unrecognized_options.join(", ")}" self.resolver = resolver self.spec = spec self.cyclic = !!options[:cyclic] @level = 0 end def resolve(manifests) manifests = index_by(manifests, &:name) if manifests.kind_of?(Array) queue = spec.dependencies + sourced_dependencies_for_manifests(manifests) state = State.new(manifests.dup, [], queue) do_resolve(state) end private def do_resolve(state) stack = [state] while !stack.empty? do state = stack.pop shift_resolved_enqueued_dependencies(state) or return state.queue.empty? and return state.manifests state.dependencies << state.queue.shift dependency = state.dependencies.last resolving_dependency_map_find_manifests(dependency) do |manifest| check_manifest(state, manifest) or next manifest.exclude_dependencies!(spec.exclusions).each do |d| debug { "Excluding dependency #{d.name} from #{manifest.name}" } end check_manifest_for_cycles(state, manifest) or next unless cyclic m = state.manifests.merge(dependency.name => manifest) a = sourced_dependencies_for_manifest(manifest) s = State.new(m, state.dependencies.dup, state.queue + a) stack.push(s) end end end def find_inconsistency(state, dependency) m = state.manifests[dependency.name] dependency.satisfied_by?(m) or return m if m violation = lambda{|d| !dependency.consistent_with?(d)} state.dependencies.find(&violation) || state.queue.find(&violation) 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(state) while (d = state.queue.first) && state.manifests[d.name] if q = find_inconsistency(state, d) debug_conflict d, q return false end state.dependencies << state.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(state, manifest) violation = lambda{|d| d.name == manifest.name && !d.satisfied_by?(manifest)} if q = state.dependencies.find(&violation) || state.queue.find(&violation) debug_conflict manifest, q return false end true end # When using this method, you are required to check the return value. # Returns +true+ if the manifest does not introduce a cycle. # Returns +false+ if the manifest introduces a cycle. def check_manifest_for_cycles(state, manifest) manifests = state.manifests.merge(manifest.name => manifest) known = manifests.keys graph = Hash[manifests.map{|n, m| [n, m.dependencies.map(&:name) & known]}] if Algorithms::AdjacencyListDirectedGraph.cyclic?(graph) debug_cycle manifest 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.class.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 debug_cycle(manifest) debug { "Cycle with #{manifest}" } 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 librarianp-0.6.4/.gitignore0000644000004100000410000000003713172517676015703 0ustar www-datawww-data*.gem .bundle pkg/* tmp vendor librarianp-0.6.4/VERSION0000644000004100000410000000000613172517676014757 0ustar www-datawww-data0.6.4 librarianp-0.6.4/CHANGELOG.md0000644000004100000410000002144113172517676015526 0ustar www-datawww-data# Change Log ## 0.6.4 * Add support for r10k Puppetfile's opts * Fix deprecation warnings * Fix compatibility with git 2.14.0 ## 0.6.3 * Prevent losing specfile path when metadata doesn't exist ## 0.6.2 * Fix bad variable name ## 0.6.1 * Fix dependencies not being fetched ## 0.6.0 * Merge duplicated dependencies in spec * Always fetch the commit SHA from rev-parse ## 0.5.0 * Allow defining exclusions in spec file * Allow forward slash in lock file ## 0.4.0 * Resolve iteratively instead of recursively * Fail if there are duplicated dependencies in spec file * Merge duplicated dependencies and warn the user ## 0.3.0 * Allow customizing default specfile and receiver downstream ## 0.2.0 * Remove highline gem not actually needed * Handle version ranges and 1.x type strings * Handle prerelease versions and semver * Add a logger warn method * Add a better error message if module doesn't exist while resolving dependencies ## 0.1.2 * \#153. Mark the license in the gemspec. * \#158, \#160, \#159. Handle cyclic dependencies for adapters which opt in to cyclic dependencies. * \#154. Cache git repository sources which are identical but for their :path attributes (where to look inside the repository for the manifest). Thanks @bcatlin. * \#161. Fix JRuby exception in posix run! method. Thanks @justenwalker. * \#163. Don't fetch git repositories multiple times per run. Thanks @njam. ## 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. librarianp-0.6.4/README.md0000644000004100000410000000326713172517676015202 0ustar www-datawww-dataLibrarian [![Build Status](https://secure.travis-ci.org/voxpupuli/librarian.png)](https://travis-ci.org/voxpupuli/librarian) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/carlossg/librarian) ========= This is a forked version published as `librarianp` with improvements in order to support `librarian-puppet`. 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 and Carlos Sanchez. Copyright (c) 2011-2013 ApplicationsOnline, LLC. and Carlos Sanchez Released under the terms of the MIT License. For further information, please see the file `LICENSE.txt`. librarianp-0.6.4/librarianp.gemspec0000644000004100000410000000205513172517676017405 0ustar www-datawww-data# -*- encoding: utf-8 -*- lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) Gem::Specification.new do |gem| gem.name = "librarianp" gem.version = File.read(File.expand_path("../VERSION", __FILE__)).strip gem.authors = ["Jay Feldblum", "Carlos Sanchez"] gem.email = ["y_feldblum@yahoo.com", "carlos@apache.org"] gem.summary = %q{A Framework for Bundlers.} gem.description = %q{A Framework for Bundlers, used by librarian-puppet.} gem.homepage = "https://github.com/voxpupuli/librarian" gem.license = "MIT" gem.files = `git ls-files`.split($/) gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) gem.require_paths = ["lib"] gem.add_dependency "thor", "~> 0.15" gem.add_development_dependency "rake" gem.add_development_dependency "rspec", "~> 2.14" gem.add_development_dependency "json" gem.add_development_dependency "fakefs", "~> 0.4.2" end