librarian 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/.gitignore +5 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +4 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +46 -0
  6. data/Rakefile +13 -0
  7. data/bin/librarian-chef +7 -0
  8. data/bin/librarian-mock +7 -0
  9. data/config/cucumber.yaml +1 -0
  10. data/features/chef/cli/install.feature +69 -0
  11. data/features/support/env.rb +5 -0
  12. data/lib/librarian.rb +191 -0
  13. data/lib/librarian/chef.rb +1 -0
  14. data/lib/librarian/chef/cli.rb +14 -0
  15. data/lib/librarian/chef/dsl.rb +14 -0
  16. data/lib/librarian/chef/extension.rb +24 -0
  17. data/lib/librarian/chef/manifest.rb +43 -0
  18. data/lib/librarian/chef/particularity.rb +9 -0
  19. data/lib/librarian/chef/source.rb +3 -0
  20. data/lib/librarian/chef/source/git.rb +14 -0
  21. data/lib/librarian/chef/source/local.rb +80 -0
  22. data/lib/librarian/chef/source/path.rb +14 -0
  23. data/lib/librarian/chef/source/site.rb +271 -0
  24. data/lib/librarian/cli.rb +76 -0
  25. data/lib/librarian/dependency.rb +44 -0
  26. data/lib/librarian/dsl.rb +76 -0
  27. data/lib/librarian/dsl/receiver.rb +46 -0
  28. data/lib/librarian/dsl/target.rb +164 -0
  29. data/lib/librarian/helpers.rb +13 -0
  30. data/lib/librarian/helpers/debug.rb +35 -0
  31. data/lib/librarian/lockfile.rb +31 -0
  32. data/lib/librarian/lockfile/compiler.rb +69 -0
  33. data/lib/librarian/lockfile/parser.rb +102 -0
  34. data/lib/librarian/manifest.rb +88 -0
  35. data/lib/librarian/manifest_set.rb +131 -0
  36. data/lib/librarian/mock.rb +1 -0
  37. data/lib/librarian/mock/cli.rb +14 -0
  38. data/lib/librarian/mock/dsl.rb +14 -0
  39. data/lib/librarian/mock/extension.rb +28 -0
  40. data/lib/librarian/mock/particularity.rb +7 -0
  41. data/lib/librarian/mock/source.rb +1 -0
  42. data/lib/librarian/mock/source/mock.rb +88 -0
  43. data/lib/librarian/mock/source/mock/registry.rb +79 -0
  44. data/lib/librarian/particularity.rb +7 -0
  45. data/lib/librarian/resolution.rb +36 -0
  46. data/lib/librarian/resolver.rb +139 -0
  47. data/lib/librarian/source.rb +2 -0
  48. data/lib/librarian/source/git.rb +91 -0
  49. data/lib/librarian/source/git/repository.rb +82 -0
  50. data/lib/librarian/source/local.rb +33 -0
  51. data/lib/librarian/source/path.rb +52 -0
  52. data/lib/librarian/spec.rb +11 -0
  53. data/lib/librarian/spec_change_set.rb +169 -0
  54. data/lib/librarian/specfile.rb +16 -0
  55. data/lib/librarian/support/abstract_method.rb +21 -0
  56. data/lib/librarian/ui.rb +64 -0
  57. data/lib/librarian/version.rb +3 -0
  58. data/librarian.gemspec +29 -0
  59. data/spec/chef/git_source_spec.rb +93 -0
  60. data/spec/dsl_spec.rb +167 -0
  61. data/spec/lockfile_spec.rb +44 -0
  62. data/spec/meta/requires_spec.rb +27 -0
  63. data/spec/resolver_spec.rb +172 -0
  64. data/spec/spec_change_set_spec.rb +165 -0
  65. metadata +172 -0
@@ -0,0 +1,52 @@
1
+ require 'librarian/particularity'
2
+ require 'librarian/source/local'
3
+
4
+ module Librarian
5
+ module Source
6
+ class Path
7
+
8
+ include Particularity
9
+ include Local
10
+
11
+ class << self
12
+ LOCK_NAME = 'PATH'
13
+ def lock_name
14
+ LOCK_NAME
15
+ end
16
+ def from_lock_options(options)
17
+ new(options[:remote], options.reject{|k, v| k == :remote})
18
+ end
19
+ end
20
+
21
+ attr_reader :path
22
+
23
+ def initialize(path, options)
24
+ @path = Pathname.new(path).expand_path(root_module.project_path)
25
+ end
26
+
27
+ def to_s
28
+ path.to_s
29
+ end
30
+
31
+ def ==(other)
32
+ other &&
33
+ self.class == other.class &&
34
+ self.path == other.path
35
+ end
36
+
37
+ def to_spec_args
38
+ [path.to_s, {}]
39
+ end
40
+
41
+ def to_lock_options
42
+ absolute_path = path.absolute? ? path : path.expand_path(root_module.project_path)
43
+ relative_path = path.relative? ? path : path.relative_path_from(root_module.project_path)
44
+ {:remote => relative_path.to_s[0, 3] == '../' ? absolute_path : relative_path}
45
+ end
46
+
47
+ def cache!(dependencies)
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,11 @@
1
+ module Librarian
2
+ class Spec
3
+
4
+ attr_reader :source, :dependencies
5
+
6
+ def initialize(source, dependencies)
7
+ @source, @dependencies = source, dependencies
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,169 @@
1
+ require 'librarian/helpers'
2
+ require 'librarian/helpers/debug'
3
+
4
+ require 'librarian/manifest_set'
5
+ require 'librarian/resolution'
6
+ require 'librarian/spec'
7
+
8
+ module Librarian
9
+ class SpecChangeSet
10
+
11
+ include Helpers::Debug
12
+
13
+ attr_reader :root_module
14
+ attr_reader :spec, :lock
15
+
16
+ def initialize(root_module, spec, lock)
17
+ @root_module = root_module
18
+ raise TypeError, "can't convert #{spec.class} into Spec" unless Spec === spec
19
+ raise TypeError, "can't convert #{lock.class} into Resolution" unless Resolution === lock
20
+ @spec, @lock = spec, lock
21
+ end
22
+
23
+ def same?
24
+ @same ||= spec.dependencies.sort_by{|d| d.name} == lock.dependencies.sort_by{|d| d.name}
25
+ end
26
+
27
+ def changed?
28
+ !same?
29
+ end
30
+
31
+ def spec_dependencies
32
+ @spec_dependencies ||= spec.dependencies
33
+ end
34
+ def spec_dependency_names
35
+ @spec_dependency_names ||= Set.new(spec_dependencies.map{|d| d.name})
36
+ end
37
+ def spec_dependency_index
38
+ @spec_dependency_index ||= Hash[spec_dependencies.map{|d| [d.name, d]}]
39
+ end
40
+
41
+ def lock_dependencies
42
+ @lock_dependencies ||= lock.dependencies
43
+ end
44
+ def lock_dependency_names
45
+ @lock_dependency_names ||= Set.new(lock_dependencies.map{|d| d.name})
46
+ end
47
+ def lock_dependency_index
48
+ @lock_dependency_index ||= Hash[lock_dependencies.map{|d| [d.name, d]}]
49
+ end
50
+
51
+ def lock_manifests
52
+ @lock_manifests ||= lock.manifests
53
+ end
54
+ def lock_manifests_index
55
+ @lock_manifests_index ||= ManifestSet.new(lock_manifests).to_hash
56
+ end
57
+
58
+ def removed_dependency_names
59
+ @removed_dependency_names ||= lock_dependency_names - spec_dependency_names
60
+ end
61
+
62
+ # A dependency which is deleted from the specfile will, in the general case,
63
+ # be removed conservatively. This means it might not actually be removed.
64
+ # But if the dependency originally declared a source which is now non-
65
+ # default, it must be removed, even if another dependency has a transitive
66
+ # dependency on the one that was removed (which is the scenario in which
67
+ # a conservative removal would not remove it). In this case, we must also
68
+ # remove it explicitly so that it can be re-resolved from the default
69
+ # source.
70
+ def explicit_removed_dependency_names
71
+ @explicit_removed_dependency_names ||= removed_dependency_names.reject do |name|
72
+ lock_manifest = lock_manifests_index[name]
73
+ lock_manifest.source == spec.source
74
+ end.to_set
75
+ end
76
+
77
+ def added_dependency_names
78
+ @added_dependency_names ||= spec_dependency_names - lock_dependency_names
79
+ end
80
+
81
+ def nonmatching_added_dependency_names
82
+ @nonmatching_added_dependency_names ||= added_dependency_names.reject do |name|
83
+ spec_dependency = spec_dependency_index[name]
84
+ lock_manifest = lock_manifests_index[name]
85
+ if lock_manifest
86
+ matching = true
87
+ matching &&= spec_dependency.satisfied_by?(lock_manifest)
88
+ matching &&= spec_dependency.source == lock_manifest.source
89
+ matching
90
+ else
91
+ false
92
+ end
93
+ end.to_set
94
+ end
95
+
96
+ def common_dependency_names
97
+ @common_dependency_names ||= lock_dependency_names & spec_dependency_names
98
+ end
99
+
100
+ def changed_dependency_names
101
+ @changed_dependency_names ||= common_dependency_names.reject do |name|
102
+ spec_dependency = spec_dependency_index[name]
103
+ lock_dependency = lock_dependency_index[name]
104
+ lock_manifest = lock_manifests_index[name]
105
+ same = true
106
+ same &&= spec_dependency.satisfied_by?(lock_manifest)
107
+ same &&= spec_dependency.source == lock_dependency.source
108
+ same
109
+ end.to_set
110
+ end
111
+
112
+ def deep_keep_manifest_names
113
+ @deep_keep_manifest_names ||= begin
114
+ lock_dependency_names - (
115
+ removed_dependency_names +
116
+ changed_dependency_names +
117
+ nonmatching_added_dependency_names
118
+ )
119
+ end
120
+ end
121
+
122
+ def shallow_strip_manifest_names
123
+ @shallow_strip_manifest_names ||= begin
124
+ explicit_removed_dependency_names + changed_dependency_names
125
+ end
126
+ end
127
+
128
+ def inspect
129
+ Helpers.strip_heredoc(<<-INSPECT)
130
+ <##{self.class.name}:
131
+ Removed: #{removed_dependency_names.to_a.join(", ")}
132
+ ExplicitRemoved: #{explicit_removed_dependency_names.to_a.join(", ")}
133
+ Added: #{added_dependency_names.to_a.join(", ")}
134
+ NonMatchingAdded: #{nonmatching_added_dependency_names.to_a.join(", ")}
135
+ Changed: #{changed_dependency_names.to_a.join(", ")}
136
+ DeepKeep: #{deep_keep_manifest_names.to_a.join(", ")}
137
+ ShallowStrip: #{shallow_strip_manifest_names.to_a.join(", ")}
138
+ >
139
+ INSPECT
140
+ end
141
+
142
+ # Returns an array of those manifests from the previous spec which should be kept,
143
+ # based on inspecting the new spec against the locked resolution from the previous spec.
144
+ def analyze
145
+ @analyze ||= begin
146
+ debug { "Analyzing spec and lock:" }
147
+
148
+ if same?
149
+ debug { " Same!" }
150
+ return lock.manifests
151
+ end
152
+
153
+ debug { " Removed:" } ; removed_dependency_names.each { |name| debug { " #{name}" } }
154
+ debug { " ExplicitRemoved:" } ; explicit_removed_dependency_names.each { |name| debug { " #{name}" } }
155
+ debug { " Added:" } ; added_dependency_names.each { |name| debug { " #{name}" } }
156
+ debug { " NonMatchingAdded:" } ; nonmatching_added_dependency_names.each { |name| debug { " #{name}" } }
157
+ debug { " Changed:" } ; changed_dependency_names.each { |name| debug { " #{name}" } }
158
+ debug { " DeepKeep:" } ; deep_keep_manifest_names.each { |name| debug { " #{name}" } }
159
+ debug { " ShallowStrip:" } ; shallow_strip_manifest_names.each { |name| debug { " #{name}" } }
160
+
161
+ manifests = ManifestSet.new(lock_manifests)
162
+ manifests.deep_keep!(deep_keep_manifest_names)
163
+ manifests.shallow_strip!(shallow_strip_manifest_names)
164
+ manifests.to_a
165
+ end
166
+ end
167
+
168
+ end
169
+ end
@@ -0,0 +1,16 @@
1
+ module Librarian
2
+ class Specfile
3
+
4
+ attr_reader :root_module, :path, :dependencies, :source
5
+
6
+ def initialize(root_module, path)
7
+ @root_module = root_module
8
+ @path = path
9
+ end
10
+
11
+ def read(precache_sources = [])
12
+ root_module.dsl_class.run(self, precache_sources)
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,21 @@
1
+ module Librarian
2
+ module Support
3
+ module AbstractMethod
4
+
5
+ class << self
6
+ def included(base)
7
+ base.extend ClassMethods
8
+ end
9
+ end
10
+
11
+ module ClassMethods
12
+ def abstract_method(*names)
13
+ names.reject{|name| respond_to?(name)}.each do |name, *args|
14
+ define_method(name) { raise Exception, "Method #{self.class.name}##{name} is abstract!" }
15
+ end
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,64 @@
1
+ require 'rubygems/user_interaction'
2
+
3
+ module Librarian
4
+ class UI
5
+ def warn(message = nil)
6
+ end
7
+
8
+ def debug(message = nil)
9
+ end
10
+
11
+ def error(message = nil)
12
+ end
13
+
14
+ def info(message = nil)
15
+ end
16
+
17
+ def confirm(message = nil)
18
+ end
19
+
20
+ class Shell < UI
21
+ attr_writer :shell
22
+ attr_reader :debug_line_numbers
23
+
24
+ def initialize(shell)
25
+ @shell = shell
26
+ @quiet = false
27
+ @debug = ENV['DEBUG']
28
+ @debug_line_numbers = false
29
+ end
30
+
31
+ def debug(message = nil)
32
+ @shell.say(message || yield) if @debug && !@quiet
33
+ end
34
+
35
+ def info(message = nil)
36
+ @shell.say(message || yield) if !@quiet
37
+ end
38
+
39
+ def confirm(message = nil)
40
+ @shell.say(message || yield, :green) if !@quiet
41
+ end
42
+
43
+ def warn(message = nil)
44
+ @shell.say(message || yield, :yellow)
45
+ end
46
+
47
+ def error(message = nil)
48
+ @shell.say(message || yield, :red)
49
+ end
50
+
51
+ def be_quiet!
52
+ @quiet = true
53
+ end
54
+
55
+ def debug!
56
+ @debug = true
57
+ end
58
+
59
+ def debug_line_numbers!
60
+ @debug_line_numbers = true
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,3 @@
1
+ module Librarian
2
+ VERSION = "0.0.1"
3
+ end
data/librarian.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "librarian/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "librarian"
7
+ s.version = Librarian::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Jay Feldblum"]
10
+ s.email = ["y_feldblum@yahoo.com"]
11
+ s.homepage = ""
12
+ s.summary = %q{Librarian}
13
+ s.description = %q{Librarian}
14
+
15
+ s.rubyforge_project = "librarian"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency "thor"
23
+
24
+ s.add_development_dependency "rspec"
25
+ s.add_development_dependency "cucumber"
26
+ s.add_development_dependency "aruba"
27
+
28
+ s.add_development_dependency "chef", ">= 0.10"
29
+ end
@@ -0,0 +1,93 @@
1
+ require 'pathname'
2
+ require 'securerandom'
3
+
4
+ require 'librarian'
5
+ require 'librarian/helpers'
6
+ require 'librarian/chef'
7
+
8
+ module Librarian
9
+ module Chef
10
+ module Source
11
+ describe Git do
12
+
13
+ project_path = Pathname.new(__FILE__).expand_path
14
+ project_path = project_path.dirname until project_path.join("Rakefile").exist?
15
+ tmp_path = project_path.join("tmp/spec/chef/git-source")
16
+ sample_path = tmp_path.join('sample')
17
+ sample_metadata = Helpers.strip_heredoc(<<-METADATA)
18
+ version '0.6.5'
19
+ METADATA
20
+
21
+ before :all do
22
+ sample_path.rmtree if sample_path.exist?
23
+ sample_path.mkpath
24
+ sample_path.join('metadata.rb').open('wb') { |f| f.write(sample_metadata) }
25
+ Dir.chdir(sample_path) do
26
+ `git init`
27
+ `git add metadata.rb`
28
+ `git commit -m "Initial commit."`
29
+ end
30
+ end
31
+
32
+ context "a single dependency with a git source" do
33
+
34
+ it "should resolve" do
35
+ repo_path = tmp_path.join('repo/resolve')
36
+ repo_path.rmtree if repo_path.exist?
37
+ repo_path.mkpath
38
+ repo_path.join('cookbooks').mkpath
39
+ cheffile = Helpers.strip_heredoc(<<-CHEFFILE)
40
+ #!/usr/bin/env ruby
41
+ cookbook "sample", :git => #{sample_path.to_s.inspect}
42
+ CHEFFILE
43
+ repo_path.join('Cheffile').open('wb') { |f| f.write(cheffile) }
44
+ Chef.stub!(:project_path) { repo_path }
45
+
46
+ Chef.resolve!
47
+ repo_path.join('Cheffile.lock').should be_exist
48
+ repo_path.join('cookbooks/sample').should_not be_exist
49
+ end
50
+
51
+ it "should install" do
52
+ repo_path = tmp_path.join('repo/install')
53
+ repo_path.rmtree if repo_path.exist?
54
+ repo_path.mkpath
55
+ repo_path.join('cookbooks').mkpath
56
+ cheffile = Helpers.strip_heredoc(<<-CHEFFILE)
57
+ #!/usr/bin/env ruby
58
+ cookbook "sample", :git => #{sample_path.to_s.inspect}
59
+ CHEFFILE
60
+ repo_path.join('Cheffile').open('wb') { |f| f.write(cheffile) }
61
+ Chef.stub!(:project_path) { repo_path }
62
+
63
+ Chef.install!
64
+ repo_path.join('Cheffile.lock').should be_exist
65
+ repo_path.join('cookbooks/sample').should be_exist
66
+ repo_path.join('cookbooks/sample/metadata.rb').should be_exist
67
+ end
68
+
69
+ it "should resolve and separately install" do
70
+ repo_path = tmp_path.join('repo/resolve-install')
71
+ repo_path.rmtree if repo_path.exist?
72
+ repo_path.mkpath
73
+ repo_path.join('cookbooks').mkpath
74
+ cheffile = Helpers.strip_heredoc(<<-CHEFFILE)
75
+ #!/usr/bin/env ruby
76
+ cookbook "sample", :git => #{sample_path.to_s.inspect}
77
+ CHEFFILE
78
+ repo_path.join('Cheffile').open('wb') { |f| f.write(cheffile) }
79
+ Chef.stub!(:project_path) { repo_path }
80
+
81
+ Chef.resolve!
82
+ repo_path.join('tmp').rmtree if repo_path.join('tmp').exist?
83
+ Chef.install!
84
+ repo_path.join('cookbooks/sample').should be_exist
85
+ repo_path.join('cookbooks/sample/metadata.rb').should be_exist
86
+ end
87
+
88
+ end
89
+
90
+ end
91
+ end
92
+ end
93
+ end