librarian 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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