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.
- data/.gitignore +4 -0
- data/CHANGELOG.md +21 -0
- data/README.md +6 -1
- data/lib/librarian/action/persist_resolution_mixin.rb +51 -0
- data/lib/librarian/action/resolve.rb +3 -38
- data/lib/librarian/action/update.rb +4 -38
- data/lib/librarian/chef/dsl.rb +1 -0
- data/lib/librarian/chef/source.rb +1 -0
- data/lib/librarian/chef/source/github.rb +27 -0
- data/lib/librarian/chef/source/site.rb +51 -51
- data/lib/librarian/cli.rb +31 -23
- data/lib/librarian/cli/manifest_presenter.rb +36 -22
- data/lib/librarian/dependency.rb +60 -0
- data/lib/librarian/environment.rb +13 -1
- data/lib/librarian/linter/source_linter.rb +55 -0
- data/lib/librarian/lockfile/parser.rb +39 -16
- data/lib/librarian/manifest.rb +8 -0
- data/lib/librarian/manifest_set.rb +5 -7
- data/lib/librarian/mock/source/mock.rb +4 -21
- data/lib/librarian/resolution.rb +1 -1
- data/lib/librarian/resolver.rb +15 -12
- data/lib/librarian/resolver/implementation.rb +166 -75
- data/lib/librarian/source/basic_api.rb +45 -0
- data/lib/librarian/source/git.rb +4 -22
- data/lib/librarian/source/git/repository.rb +1 -1
- data/lib/librarian/source/local.rb +0 -7
- data/lib/librarian/source/path.rb +4 -22
- data/lib/librarian/version.rb +1 -1
- data/librarian.gemspec +3 -3
- data/spec/functional/chef/source/site_spec.rb +150 -100
- data/spec/functional/source/git/repository_spec.rb +2 -1
- data/spec/{functional → integration}/chef/source/git_spec.rb +12 -3
- data/spec/integration/chef/source/site_spec.rb +217 -0
- data/spec/support/cli_macro.rb +4 -12
- data/spec/support/method_patch_macro.rb +30 -0
- data/spec/unit/config/database_spec.rb +8 -0
- data/spec/unit/dependency_spec.rb +176 -0
- data/spec/unit/environment_spec.rb +76 -7
- data/spec/unit/resolver_spec.rb +2 -2
- metadata +52 -46
data/lib/librarian/cli.rb
CHANGED
@@ -23,18 +23,20 @@ module Librarian
|
|
23
23
|
|
24
24
|
class << self
|
25
25
|
def bin!
|
26
|
-
with_environment
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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)
|
127
|
-
|
128
|
-
|
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
|
-
|
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
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
data/lib/librarian/dependency.rb
CHANGED
@@ -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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
71
|
+
dependencies
|
63
72
|
end
|
64
73
|
|
65
|
-
|
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.
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
data/lib/librarian/manifest.rb
CHANGED
@@ -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 :
|
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
|