librarian 0.0.25 → 0.0.26

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 (40) hide show
  1. data/.gitignore +4 -0
  2. data/CHANGELOG.md +21 -0
  3. data/README.md +6 -1
  4. data/lib/librarian/action/persist_resolution_mixin.rb +51 -0
  5. data/lib/librarian/action/resolve.rb +3 -38
  6. data/lib/librarian/action/update.rb +4 -38
  7. data/lib/librarian/chef/dsl.rb +1 -0
  8. data/lib/librarian/chef/source.rb +1 -0
  9. data/lib/librarian/chef/source/github.rb +27 -0
  10. data/lib/librarian/chef/source/site.rb +51 -51
  11. data/lib/librarian/cli.rb +31 -23
  12. data/lib/librarian/cli/manifest_presenter.rb +36 -22
  13. data/lib/librarian/dependency.rb +60 -0
  14. data/lib/librarian/environment.rb +13 -1
  15. data/lib/librarian/linter/source_linter.rb +55 -0
  16. data/lib/librarian/lockfile/parser.rb +39 -16
  17. data/lib/librarian/manifest.rb +8 -0
  18. data/lib/librarian/manifest_set.rb +5 -7
  19. data/lib/librarian/mock/source/mock.rb +4 -21
  20. data/lib/librarian/resolution.rb +1 -1
  21. data/lib/librarian/resolver.rb +15 -12
  22. data/lib/librarian/resolver/implementation.rb +166 -75
  23. data/lib/librarian/source/basic_api.rb +45 -0
  24. data/lib/librarian/source/git.rb +4 -22
  25. data/lib/librarian/source/git/repository.rb +1 -1
  26. data/lib/librarian/source/local.rb +0 -7
  27. data/lib/librarian/source/path.rb +4 -22
  28. data/lib/librarian/version.rb +1 -1
  29. data/librarian.gemspec +3 -3
  30. data/spec/functional/chef/source/site_spec.rb +150 -100
  31. data/spec/functional/source/git/repository_spec.rb +2 -1
  32. data/spec/{functional → integration}/chef/source/git_spec.rb +12 -3
  33. data/spec/integration/chef/source/site_spec.rb +217 -0
  34. data/spec/support/cli_macro.rb +4 -12
  35. data/spec/support/method_patch_macro.rb +30 -0
  36. data/spec/unit/config/database_spec.rb +8 -0
  37. data/spec/unit/dependency_spec.rb +176 -0
  38. data/spec/unit/environment_spec.rb +76 -7
  39. data/spec/unit/resolver_spec.rb +2 -2
  40. metadata +52 -46
@@ -23,18 +23,20 @@ module Librarian
23
23
 
24
24
  class << self
25
25
  def bin!
26
- with_environment do |environment|
27
- begin
28
- start
29
- rescue Librarian::Error => e
30
- environment.ui.error e.message
31
- environment.ui.debug e.backtrace.join("\n")
32
- exit (e.respond_to?(:status_code) ? e.status_code : 1)
33
- rescue Interrupt => e
34
- environment.ui.error "\nQuitting..."
35
- exit 1
36
- end
37
- end
26
+ status = with_environment { returning_status { start } }
27
+ exit status
28
+ end
29
+
30
+ def returning_status
31
+ yield
32
+ 0
33
+ rescue Librarian::Error => e
34
+ environment.ui.error e.message
35
+ environment.ui.debug e.backtrace.join("\n")
36
+ e.respond_to?(:status_code) && e.status_code || 1
37
+ rescue Interrupt => e
38
+ environment.ui.error "\nQuitting..."
39
+ 1
38
40
  end
39
41
 
40
42
  attr_accessor :environment
@@ -74,16 +76,13 @@ module Librarian
74
76
  if key
75
77
  raise Error, "cannot set both value and delete" if value && options["delete"]
76
78
  if options["delete"]
77
- raise Error, "must set either global or local" unless options["global"] ^ options["local"]
78
- scope = options["global"] ? :global : options["local"] ? :local : nil
79
+ scope = config_scope(true)
79
80
  environment.config_db[key, scope] = nil
80
81
  elsif value
81
- raise Error, "must set either global or local" unless options["global"] ^ options["local"]
82
- scope = options["global"] ? :global : options["local"] ? :local : nil
82
+ scope = config_scope(true)
83
83
  environment.config_db[key, scope] = value
84
84
  else
85
- raise Error, "cannot set both global and local" if options["global"] && options["local"]
86
- scope = options["global"] ? :global : options["local"] ? :local : nil
85
+ scope = config_scope(false)
87
86
  if value = environment.config_db[key, scope]
88
87
  prefix = scope ? "#{key} (#{scope})" : key
89
88
  say "#{prefix}: #{value}"
@@ -123,11 +122,9 @@ module Librarian
123
122
  def outdated
124
123
  ensure!
125
124
  resolution = environment.lock
126
- resolution.manifests.sort_by(&:name).each do |manifest|
127
- source = manifest.source
128
- source_manifest = source.manifests(manifest.name).first
129
- next if manifest.version == source_manifest.version
130
- say "#{manifest.name} (#{manifest.version} -> #{source_manifest.version})"
125
+ manifests = resolution.manifests.sort_by(&:name)
126
+ manifests.select(&:outdated?).each do |manifest|
127
+ say "#{manifest.name} (#{manifest.version} -> #{manifest.latest.version})"
131
128
  end
132
129
  end
133
130
 
@@ -211,5 +208,16 @@ module Librarian
211
208
  environment.logger.relative_path_to(path)
212
209
  end
213
210
 
211
+ def config_scope(exclusive)
212
+ g, l = "global", "local"
213
+ if exclusive
214
+ options[g] ^ options[l] or raise Error, "must set either #{g} or #{l}"
215
+ else
216
+ options[g] && options[l] and raise Error, "cannot set both #{g} and #{l}"
217
+ end
218
+
219
+ options[g] ? :global : options[l] ? :local : nil
220
+ end
221
+
214
222
  end
215
223
  end
@@ -17,40 +17,47 @@ module Librarian
17
17
  full = options[:detailed]
18
18
  full = !names.empty? if full.nil?
19
19
 
20
- if names.empty?
21
- names = manifests.map(&:name).sort if names.empty?
22
- else
23
- missing_names = names.reject{|name| manifest(name)}
24
- unless missing_names.empty?
25
- raise Error, "not found: #{missing_names.map(&:inspect).join(', ')}"
26
- end
27
- end
20
+ names = manifests.map(&:name).sort if names.empty?
21
+ assert_no_manifests_missing!(names)
28
22
 
29
- names.each do |name|
30
- manifest = manifest(name)
31
- present_one(manifest, :detailed => full)
32
- end
23
+ present_each(names, :detailed => full)
33
24
  end
34
25
 
35
26
  def present_one(manifest, options = { })
36
27
  full = options[:detailed]
37
28
 
38
29
  say "#{manifest.name} (#{manifest.version})" do
39
- if full
40
- say "source: #{manifest.source}"
41
- unless manifest.dependencies.empty?
42
- say "dependencies:" do
43
- manifest.dependencies.sort_by(&:name).each do |dependency|
44
- say "#{dependency.name} (#{dependency.requirement})"
45
- end
46
- end
47
- end
48
- end
30
+ full or next
31
+
32
+ present_one_source(manifest)
33
+ present_one_dependencies(manifest)
49
34
  end
50
35
  end
51
36
 
52
37
  private
53
38
 
39
+ def present_each(names, options)
40
+ names.each do |name|
41
+ manifest = manifest(name)
42
+ present_one(manifest, options)
43
+ end
44
+ end
45
+
46
+ def present_one_source(manifest)
47
+ say "source: #{manifest.source}"
48
+ end
49
+
50
+ def present_one_dependencies(manifest)
51
+ manifest.dependencies.empty? and return
52
+
53
+ say "dependencies:" do
54
+ deps = manifest.dependencies.sort_by(&:name)
55
+ deps.each do |dependency|
56
+ say "#{dependency.name} (#{dependency.requirement})"
57
+ end
58
+ end
59
+ end
60
+
54
61
  attr_accessor :scope_level, :manifests_index
55
62
 
56
63
  def manifest(name)
@@ -74,6 +81,13 @@ module Librarian
74
81
  self.scope_level = original_scope_level
75
82
  end
76
83
 
84
+ def assert_no_manifests_missing!(names)
85
+ missing_names = names.reject{|name| manifest(name)}
86
+ unless missing_names.empty?
87
+ raise Error, "not found: #{missing_names.map(&:inspect).join(', ')}"
88
+ end
89
+ end
90
+
77
91
  end
78
92
  end
79
93
  end
@@ -26,6 +26,51 @@ module Librarian
26
26
  to_gem_requirement.to_s
27
27
  end
28
28
 
29
+ COMPATS_TABLE = {
30
+ %w(= = ) => lambda{|s, o| s == o},
31
+ %w(= !=) => lambda{|s, o| s != o},
32
+ %w(= > ) => lambda{|s, o| s > o},
33
+ %w(= < ) => lambda{|s, o| s < o},
34
+ %w(= >=) => lambda{|s, o| s >= o},
35
+ %w(= <=) => lambda{|s, o| s <= o},
36
+ %w(= ~>) => lambda{|s, o| s >= o && s.release < o.bump},
37
+ %w(!= !=) => true,
38
+ %w(!= > ) => true,
39
+ %w(!= < ) => true,
40
+ %w(!= >=) => true,
41
+ %w(!= <=) => true,
42
+ %w(!= ~>) => true,
43
+ %w(> > ) => true,
44
+ %w(> < ) => lambda{|s, o| s < o},
45
+ %w(> >=) => true,
46
+ %w(> <=) => lambda{|s, o| s < o},
47
+ %w(> ~>) => lambda{|s, o| s < o.bump},
48
+ %w(< < ) => true,
49
+ %w(< >=) => lambda{|s, o| s > o},
50
+ %w(< <=) => true,
51
+ %w(< ~>) => lambda{|s, o| s > o},
52
+ %w(>= >=) => true,
53
+ %w(>= <=) => lambda{|s, o| s <= o},
54
+ %w(>= ~>) => lambda{|s, o| s < o.bump},
55
+ %w(<= <=) => true,
56
+ %w(<= ~>) => lambda{|s, o| s >= o},
57
+ %w(~> ~>) => lambda{|s, o| s < o.bump && s.bump > o},
58
+ }
59
+
60
+ def consistent_with?(other)
61
+ sgreq, ogreq = to_gem_requirement, other.to_gem_requirement
62
+ sreqs, oreqs = sgreq.requirements, ogreq.requirements
63
+ sreqs.all? do |sreq|
64
+ oreqs.all? do |oreq|
65
+ compatible?(sreq, oreq)
66
+ end
67
+ end
68
+ end
69
+
70
+ def inconsistent_with?(other)
71
+ !consistent_with?(other)
72
+ end
73
+
29
74
  protected
30
75
 
31
76
  attr_accessor :backing
@@ -38,6 +83,13 @@ module Librarian
38
83
  arg
39
84
  end
40
85
  end
86
+
87
+ def compatible?(a, b)
88
+ a, b = b, a unless COMPATS_TABLE.include?([a.first, b.first])
89
+ r = COMPATS_TABLE[[a.first, b.first]]
90
+ r = r.call(a.last, b.last) if r.respond_to?(:call)
91
+ r
92
+ end
41
93
  end
42
94
 
43
95
  attr_accessor :name, :requirement, :source
@@ -77,6 +129,14 @@ module Librarian
77
129
  self.source == other.source
78
130
  end
79
131
 
132
+ def consistent_with?(other)
133
+ name != other.name || requirement.consistent_with?(other.requirement)
134
+ end
135
+
136
+ def inconsistent_with?(other)
137
+ !consistent_with?(other)
138
+ end
139
+
80
140
  private
81
141
 
82
142
  def assert_name_valid!(name)
@@ -153,7 +153,9 @@ module Librarian
153
153
  end
154
154
  end
155
155
 
156
- def net_http_class
156
+ def net_http_class(host)
157
+ return Net::HTTP if no_proxy?(host)
158
+
157
159
  @net_http_class ||= begin
158
160
  p = http_proxy_uri
159
161
  p ? Net::HTTP::Proxy(p.host, p.port, p.user, p.password) : Net::HTTP
@@ -166,5 +168,15 @@ module Librarian
166
168
  self
167
169
  end
168
170
 
171
+ def no_proxy?(host)
172
+ @no_proxy ||= begin
173
+ list = ENV['NO_PROXY'] || ENV['no_proxy'] || ""
174
+ list.split(/\s*,\s*/) + %w(localhost 127.0.0.1)
175
+ end
176
+ @no_proxy.any? do |host_addr|
177
+ host.match(Regexp.quote(host_addr)+'$')
178
+ end
179
+ end
180
+
169
181
  end
170
182
  end
@@ -0,0 +1,55 @@
1
+ module Librarian
2
+ module Linter
3
+ class SourceLinter
4
+
5
+ class << self
6
+ def lint!(klass)
7
+ new(klass).lint!
8
+ end
9
+ end
10
+
11
+ attr_accessor :klass
12
+ private :klass=
13
+
14
+ def initialize(klass)
15
+ self.klass = klass
16
+ end
17
+
18
+ def lint!
19
+ lint_class_responds_to! *[
20
+ :lock_name,
21
+ :from_spec_args,
22
+ :from_lock_options,
23
+ ]
24
+
25
+ lint_instance_responds_to! *[
26
+ :to_spec_args,
27
+ :to_lock_options,
28
+ :manifests,
29
+ :fetch_version,
30
+ :fetch_dependencies,
31
+ :pinned?,
32
+ :unpin!,
33
+ :install!,
34
+ ]
35
+ end
36
+
37
+ private
38
+
39
+ def lint_class_responds_to!(*names)
40
+ missing = names.reject{|name| klass.respond_to?(name)}
41
+ return if missing.empty?
42
+
43
+ raise "class must respond to #{missing.join(', ')}"
44
+ end
45
+
46
+ def lint_instance_responds_to!(*names)
47
+ missing = names - klass.public_instance_methods.map(&:to_sym)
48
+ return if missing.empty?
49
+
50
+ raise "instance must respond to #{missing.join(', ')}"
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -21,10 +21,18 @@ module Librarian
21
21
  end
22
22
 
23
23
  def parse(string)
24
- string = string.dup
25
- source_type_names_map = Hash[dsl_class.source_types.map{|t| [t[1].lock_name, t[1]]}]
26
- source_type_names = dsl_class.source_types.map{|t| t[1].lock_name}
27
- lines = string.split(/(\r|\n|\r\n)+/).select{|l| l =~ /\S/}
24
+ lines = string.lines.map{|l| l.sub(/\s+\z/, '')}.reject(&:empty?)
25
+ sources = extract_and_parse_sources(lines)
26
+ manifests = compile(sources)
27
+ manifests_index = Hash[manifests.map{|m| [m.name, m]}]
28
+ raise StandardError, "Expected DEPENDENCIES topic!" unless lines.shift == "DEPENDENCIES"
29
+ dependencies = extract_and_parse_dependencies(lines, manifests_index)
30
+ Resolution.new(dependencies, manifests)
31
+ end
32
+
33
+ private
34
+
35
+ def extract_and_parse_sources(lines)
28
36
  sources = []
29
37
  while source_type_names.include?(lines.first)
30
38
  source = {}
@@ -50,21 +58,20 @@ module Librarian
50
58
  source[:manifests] = manifests
51
59
  sources << source
52
60
  end
53
- manifests = compile(sources)
54
- manifests_index = Hash[manifests.map{|m| [m.name, m]}]
55
- raise StandardError, "Expected DEPENDENCIES topic!" unless lines.shift == "DEPENDENCIES"
61
+ sources
62
+ end
63
+
64
+ def extract_and_parse_dependencies(lines, manifests_index)
56
65
  dependencies = []
57
66
  while lines.first =~ /^ {2}([\w-]+)(?: \((.*)\))?$/
58
67
  lines.shift
59
68
  name, requirement = $1, $2.split(/,\s*/)
60
69
  dependencies << Dependency.new(name, requirement, manifests_index[name].source)
61
70
  end
62
- Resolution.new(dependencies, manifests)
71
+ dependencies
63
72
  end
64
73
 
65
- private
66
-
67
- def compile(sources_ast)
74
+ def compile_placeholder_manifests(sources_ast)
68
75
  manifests = {}
69
76
  sources_ast.each do |source_ast|
70
77
  source_type = source_ast[:type]
@@ -78,15 +85,19 @@ module Librarian
78
85
  )
79
86
  end
80
87
  end
88
+ manifests
89
+ end
90
+
91
+ def compile(sources_ast)
92
+ manifests = compile_placeholder_manifests(sources_ast)
81
93
  manifests = manifests.map do |name, manifest|
82
94
  dependencies = manifest.dependencies.map do |d|
83
95
  Dependency.new(d.name, d.requirement, manifests[d.name].source)
84
96
  end
85
- manifest.source.manifest(
86
- manifest.name,
87
- manifest.version,
88
- dependencies
89
- )
97
+ real = Manifest.new(manifest.source, manifest.name)
98
+ real.version = manifest.version
99
+ real.dependencies = manifest.dependencies
100
+ real
90
101
  end
91
102
  ManifestSet.sort(manifests)
92
103
  end
@@ -95,6 +106,18 @@ module Librarian
95
106
  environment.dsl_class
96
107
  end
97
108
 
109
+ def source_type_names_map
110
+ @source_type_names_map ||= begin
111
+ Hash[dsl_class.source_types.map{|t| [t[1].lock_name, t[1]]}]
112
+ end
113
+ end
114
+
115
+ def source_type_names
116
+ @source_type_names ||= begin
117
+ dsl_class.source_types.map{|t| t[1].lock_name}
118
+ end
119
+ end
120
+
98
121
  end
99
122
  end
100
123
  end
@@ -65,6 +65,14 @@ module Librarian
65
65
  defined_version == fetched_version
66
66
  end
67
67
 
68
+ def latest
69
+ @latest ||= source.manifests(name).first
70
+ end
71
+
72
+ def outdated?
73
+ latest.version > version
74
+ end
75
+
68
76
  def dependencies
69
77
  defined_dependencies || fetched_dependencies
70
78
  end
@@ -43,7 +43,7 @@ module Librarian
43
43
  end
44
44
 
45
45
  def initialize(manifests)
46
- self.index = Hash === manifests ? manifests.dup : Hash[manifests.map{|m| [m.name, m]}]
46
+ self.index = Hash === manifests ? manifests.dup : index_by(manifests, &:name)
47
47
  end
48
48
 
49
49
  def to_a
@@ -76,9 +76,6 @@ module Librarian
76
76
  end
77
77
 
78
78
  def deep_strip!(names)
79
- names = Array === names ? names.dup : names.to_a
80
- assert_strings!(names)
81
-
82
79
  strippables = dependencies_of(names)
83
80
  shallow_strip!(strippables)
84
81
 
@@ -102,9 +99,6 @@ module Librarian
102
99
  end
103
100
 
104
101
  def deep_keep!(names)
105
- names = Array === names ? names.dup : names.to_a
106
- assert_strings!(names)
107
-
108
102
  keepables = dependencies_of(names)
109
103
  shallow_keep!(keepables)
110
104
 
@@ -149,5 +143,9 @@ module Librarian
149
143
  deps.to_a
150
144
  end
151
145
 
146
+ def index_by(enum)
147
+ Hash[enum.map{|obj| [yield(obj), obj]}]
148
+ end
149
+
152
150
  end
153
151
  end