librarianp 0.1.2
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.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/.travis.yml +10 -0
- data/CHANGELOG.md +255 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +235 -0
- data/LICENSE.txt +22 -0
- data/README.md +55 -0
- data/Rakefile +28 -0
- data/VERSION +1 -0
- data/lib/librarian/action/base.rb +24 -0
- data/lib/librarian/action/clean.rb +44 -0
- data/lib/librarian/action/ensure.rb +24 -0
- data/lib/librarian/action/install.rb +95 -0
- data/lib/librarian/action/persist_resolution_mixin.rb +51 -0
- data/lib/librarian/action/resolve.rb +46 -0
- data/lib/librarian/action/update.rb +44 -0
- data/lib/librarian/action.rb +5 -0
- data/lib/librarian/algorithms.rb +133 -0
- data/lib/librarian/cli/manifest_presenter.rb +89 -0
- data/lib/librarian/cli.rb +225 -0
- data/lib/librarian/config/database.rb +205 -0
- data/lib/librarian/config/file_source.rb +47 -0
- data/lib/librarian/config/hash_source.rb +33 -0
- data/lib/librarian/config/source.rb +149 -0
- data/lib/librarian/config.rb +7 -0
- data/lib/librarian/dependency.rb +153 -0
- data/lib/librarian/dsl/receiver.rb +42 -0
- data/lib/librarian/dsl/target.rb +171 -0
- data/lib/librarian/dsl.rb +102 -0
- data/lib/librarian/environment/runtime_cache.rb +101 -0
- data/lib/librarian/environment.rb +230 -0
- data/lib/librarian/error.rb +4 -0
- data/lib/librarian/helpers.rb +29 -0
- data/lib/librarian/linter/source_linter.rb +55 -0
- data/lib/librarian/lockfile/compiler.rb +66 -0
- data/lib/librarian/lockfile/parser.rb +123 -0
- data/lib/librarian/lockfile.rb +29 -0
- data/lib/librarian/logger.rb +46 -0
- data/lib/librarian/manifest.rb +146 -0
- data/lib/librarian/manifest_set.rb +150 -0
- data/lib/librarian/mock/cli.rb +19 -0
- data/lib/librarian/mock/dsl.rb +15 -0
- data/lib/librarian/mock/environment.rb +21 -0
- data/lib/librarian/mock/extension.rb +9 -0
- data/lib/librarian/mock/source/mock/registry.rb +83 -0
- data/lib/librarian/mock/source/mock.rb +80 -0
- data/lib/librarian/mock/source.rb +1 -0
- data/lib/librarian/mock/version.rb +5 -0
- data/lib/librarian/mock.rb +1 -0
- data/lib/librarian/posix.rb +129 -0
- data/lib/librarian/resolution.rb +46 -0
- data/lib/librarian/resolver/implementation.rb +238 -0
- data/lib/librarian/resolver.rb +94 -0
- data/lib/librarian/rspec/support/cli_macro.rb +120 -0
- data/lib/librarian/source/basic_api.rb +45 -0
- data/lib/librarian/source/git/repository.rb +193 -0
- data/lib/librarian/source/git.rb +172 -0
- data/lib/librarian/source/local.rb +54 -0
- data/lib/librarian/source/path.rb +56 -0
- data/lib/librarian/source.rb +2 -0
- data/lib/librarian/spec.rb +13 -0
- data/lib/librarian/spec_change_set.rb +173 -0
- data/lib/librarian/specfile.rb +19 -0
- data/lib/librarian/support/abstract_method.rb +21 -0
- data/lib/librarian/ui.rb +64 -0
- data/lib/librarian/version.rb +3 -0
- data/lib/librarian.rb +11 -0
- data/librarian.gemspec +47 -0
- data/spec/functional/cli_spec.rb +27 -0
- data/spec/functional/posix_spec.rb +32 -0
- data/spec/functional/source/git/repository_spec.rb +199 -0
- data/spec/functional/source/git_spec.rb +174 -0
- data/spec/support/fakefs.rb +37 -0
- data/spec/support/method_patch_macro.rb +30 -0
- data/spec/support/project_path_macro.rb +14 -0
- data/spec/support/with_env_macro.rb +22 -0
- data/spec/unit/action/base_spec.rb +18 -0
- data/spec/unit/action/clean_spec.rb +102 -0
- data/spec/unit/action/ensure_spec.rb +37 -0
- data/spec/unit/action/install_spec.rb +111 -0
- data/spec/unit/algorithms_spec.rb +131 -0
- data/spec/unit/config/database_spec.rb +320 -0
- data/spec/unit/dependency/requirement_spec.rb +12 -0
- data/spec/unit/dependency_spec.rb +212 -0
- data/spec/unit/dsl_spec.rb +173 -0
- data/spec/unit/environment/runtime_cache_spec.rb +73 -0
- data/spec/unit/environment_spec.rb +209 -0
- data/spec/unit/lockfile/parser_spec.rb +162 -0
- data/spec/unit/lockfile_spec.rb +65 -0
- data/spec/unit/manifest/version_spec.rb +11 -0
- data/spec/unit/manifest_set_spec.rb +202 -0
- data/spec/unit/manifest_spec.rb +36 -0
- data/spec/unit/mock/environment_spec.rb +25 -0
- data/spec/unit/mock/source/mock_spec.rb +22 -0
- data/spec/unit/resolver_spec.rb +299 -0
- data/spec/unit/source/git_spec.rb +29 -0
- data/spec/unit/spec_change_set_spec.rb +169 -0
- metadata +257 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
module Librarian
|
2
|
+
#
|
3
|
+
# Represents the output of the resolution process. Captures the declared
|
4
|
+
# dependencies plus the full set of resolved manifests. The sources are
|
5
|
+
# already known by the dependencies and by the resolved manifests, so they do
|
6
|
+
# not need to be captured explicitly.
|
7
|
+
#
|
8
|
+
# This representation may be produced by the resolver, may be serialized into
|
9
|
+
# a lockfile, and may be deserialized from a lockfile. It is expected that the
|
10
|
+
# lockfile is a direct representation in text of this representation, so that
|
11
|
+
# the serialization-deserialization process is just the identity function.
|
12
|
+
#
|
13
|
+
class Resolution
|
14
|
+
attr_accessor :dependencies, :manifests, :manifests_index
|
15
|
+
private :dependencies=, :manifests=, :manifests_index=
|
16
|
+
|
17
|
+
def initialize(dependencies, manifests)
|
18
|
+
self.dependencies = dependencies
|
19
|
+
self.manifests = manifests
|
20
|
+
self.manifests_index = build_manifests_index(manifests)
|
21
|
+
end
|
22
|
+
|
23
|
+
def correct?
|
24
|
+
manifests && manifests_consistent_with_dependencies? && manifests_internally_consistent?
|
25
|
+
end
|
26
|
+
|
27
|
+
def sources
|
28
|
+
manifests.map(&:source).uniq
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def build_manifests_index(manifests)
|
34
|
+
Hash[manifests.map{|m| [m.name, m]}] if manifests
|
35
|
+
end
|
36
|
+
|
37
|
+
def manifests_consistent_with_dependencies?
|
38
|
+
ManifestSet.new(manifests).in_compliance_with?(dependencies)
|
39
|
+
end
|
40
|
+
|
41
|
+
def manifests_internally_consistent?
|
42
|
+
ManifestSet.new(manifests).consistent?
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,238 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
require 'librarian/algorithms'
|
4
|
+
require 'librarian/dependency'
|
5
|
+
|
6
|
+
module Librarian
|
7
|
+
class Resolver
|
8
|
+
class Implementation
|
9
|
+
|
10
|
+
class MultiSource
|
11
|
+
attr_accessor :sources
|
12
|
+
def initialize(sources)
|
13
|
+
self.sources = sources
|
14
|
+
end
|
15
|
+
def manifests(name)
|
16
|
+
sources.reverse.map{|source| source.manifests(name)}.flatten(1).compact
|
17
|
+
end
|
18
|
+
def to_s
|
19
|
+
"(no source specified)"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class State
|
24
|
+
attr_accessor :manifests, :dependencies, :queue
|
25
|
+
private :manifests=, :dependencies=, :queue=
|
26
|
+
def initialize(manifests, dependencies, queue)
|
27
|
+
self.manifests = manifests
|
28
|
+
self.dependencies = dependencies # resolved
|
29
|
+
self.queue = queue # scheduled
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_accessor :resolver, :spec, :cyclic
|
34
|
+
private :resolver=, :spec=, :cyclic=
|
35
|
+
|
36
|
+
def initialize(resolver, spec, options = { })
|
37
|
+
unrecognized_options = options.keys - [:cyclic]
|
38
|
+
unrecognized_options.empty? or raise Error,
|
39
|
+
"unrecognized options: #{unrecognized_options.join(", ")}"
|
40
|
+
self.resolver = resolver
|
41
|
+
self.spec = spec
|
42
|
+
self.cyclic = !!options[:cyclic]
|
43
|
+
@level = 0
|
44
|
+
end
|
45
|
+
|
46
|
+
def resolve(manifests)
|
47
|
+
manifests = index_by(manifests, &:name) if manifests.kind_of?(Array)
|
48
|
+
queue = spec.dependencies + sourced_dependencies_for_manifests(manifests)
|
49
|
+
state = State.new(manifests.dup, [], queue)
|
50
|
+
recursive_resolve(state)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def recursive_resolve(state)
|
56
|
+
shift_resolved_enqueued_dependencies(state) or return
|
57
|
+
state.queue.empty? and return state.manifests
|
58
|
+
|
59
|
+
state.dependencies << state.queue.shift
|
60
|
+
dependency = state.dependencies.last
|
61
|
+
|
62
|
+
resolving_dependency_map_find_manifests(dependency) do |manifest|
|
63
|
+
check_manifest(state, manifest) or next
|
64
|
+
check_manifest_for_cycles(state, manifest) or next unless cyclic
|
65
|
+
|
66
|
+
m = state.manifests.merge(dependency.name => manifest)
|
67
|
+
a = sourced_dependencies_for_manifest(manifest)
|
68
|
+
s = State.new(m, state.dependencies.dup, state.queue + a)
|
69
|
+
|
70
|
+
recursive_resolve(s)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def find_inconsistency(state, dependency)
|
75
|
+
m = state.manifests[dependency.name]
|
76
|
+
dependency.satisfied_by?(m) or return m if m
|
77
|
+
violation = lambda{|d| !dependency.consistent_with?(d)}
|
78
|
+
state.dependencies.find(&violation) || state.queue.find(&violation)
|
79
|
+
end
|
80
|
+
|
81
|
+
# When using this method, you are required to check the return value.
|
82
|
+
# Returns +true+ if the resolved enqueued dependencies at the front of the
|
83
|
+
# queue could all be moved to the resolved dependencies list.
|
84
|
+
# Returns +false+ if there was an inconsistency when trying to move one or
|
85
|
+
# more of them.
|
86
|
+
# This modifies +queue+ and +dependencies+.
|
87
|
+
def shift_resolved_enqueued_dependencies(state)
|
88
|
+
while (d = state.queue.first) && state.manifests[d.name]
|
89
|
+
if q = find_inconsistency(state, d)
|
90
|
+
debug_conflict d, q
|
91
|
+
return false
|
92
|
+
end
|
93
|
+
state.dependencies << state.queue.shift
|
94
|
+
end
|
95
|
+
true
|
96
|
+
end
|
97
|
+
|
98
|
+
# When using this method, you are required to check the return value.
|
99
|
+
# Returns +true+ if the manifest satisfies all of the dependencies.
|
100
|
+
# Returns +false+ if there was a dependency that the manifest does not
|
101
|
+
# satisfy.
|
102
|
+
def check_manifest(state, manifest)
|
103
|
+
violation = lambda{|d| d.name == manifest.name && !d.satisfied_by?(manifest)}
|
104
|
+
if q = state.dependencies.find(&violation) || state.queue.find(&violation)
|
105
|
+
debug_conflict manifest, q
|
106
|
+
return false
|
107
|
+
end
|
108
|
+
true
|
109
|
+
end
|
110
|
+
|
111
|
+
# When using this method, you are required to check the return value.
|
112
|
+
# Returns +true+ if the manifest does not introduce a cycle.
|
113
|
+
# Returns +false+ if the manifest introduces a cycle.
|
114
|
+
def check_manifest_for_cycles(state, manifest)
|
115
|
+
manifests = state.manifests.merge(manifest.name => manifest)
|
116
|
+
known = manifests.keys
|
117
|
+
graph = Hash[manifests.map{|n, m| [n, m.dependencies.map(&:name) & known]}]
|
118
|
+
if Algorithms::AdjacencyListDirectedGraph.cyclic?(graph)
|
119
|
+
debug_cycle manifest
|
120
|
+
return false
|
121
|
+
end
|
122
|
+
true
|
123
|
+
end
|
124
|
+
|
125
|
+
def default_source
|
126
|
+
@default_source ||= MultiSource.new(spec.sources)
|
127
|
+
end
|
128
|
+
|
129
|
+
def dependency_source_map
|
130
|
+
@dependency_source_map ||=
|
131
|
+
Hash[spec.dependencies.map{|d| [d.name, d.source]}]
|
132
|
+
end
|
133
|
+
|
134
|
+
def sourced_dependency_for(dependency)
|
135
|
+
return dependency if dependency.source
|
136
|
+
|
137
|
+
source = dependency_source_map[dependency.name] || default_source
|
138
|
+
Dependency.new(dependency.name, dependency.requirement, source)
|
139
|
+
end
|
140
|
+
|
141
|
+
def sourced_dependencies_for_manifest(manifest)
|
142
|
+
manifest.dependencies.map{|d| sourced_dependency_for(d)}
|
143
|
+
end
|
144
|
+
|
145
|
+
def sourced_dependencies_for_manifests(manifests)
|
146
|
+
manifests = manifests.values if manifests.kind_of?(Hash)
|
147
|
+
manifests.map{|m| sourced_dependencies_for_manifest(m)}.flatten(1)
|
148
|
+
end
|
149
|
+
|
150
|
+
def resolving_dependency_map_find_manifests(dependency)
|
151
|
+
scope_resolving_dependency dependency do
|
152
|
+
map_find(dependency.manifests) do |manifest|
|
153
|
+
scope_checking_manifest dependency, manifest do
|
154
|
+
yield manifest
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def scope_resolving_dependency(dependency)
|
161
|
+
debug { "Resolving #{dependency}" }
|
162
|
+
resolution = nil
|
163
|
+
scope do
|
164
|
+
scope_checking_manifests do
|
165
|
+
resolution = yield
|
166
|
+
end
|
167
|
+
if resolution
|
168
|
+
debug { "Resolved #{dependency}" }
|
169
|
+
else
|
170
|
+
debug { "Failed to resolve #{dependency}" }
|
171
|
+
end
|
172
|
+
end
|
173
|
+
resolution
|
174
|
+
end
|
175
|
+
|
176
|
+
def scope_checking_manifests
|
177
|
+
debug { "Checking manifests" }
|
178
|
+
scope do
|
179
|
+
yield
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def scope_checking_manifest(dependency, manifest)
|
184
|
+
debug { "Checking #{manifest}" }
|
185
|
+
resolution = nil
|
186
|
+
scope do
|
187
|
+
resolution = yield
|
188
|
+
if resolution
|
189
|
+
debug { "Resolved #{dependency} at #{manifest}" }
|
190
|
+
else
|
191
|
+
debug { "Backtracking from #{manifest}" }
|
192
|
+
end
|
193
|
+
end
|
194
|
+
resolution
|
195
|
+
end
|
196
|
+
|
197
|
+
def debug_schedule(dependency)
|
198
|
+
debug { "Scheduling #{dependency}" }
|
199
|
+
end
|
200
|
+
|
201
|
+
def debug_conflict(dependency, conflict)
|
202
|
+
debug { "Conflict between #{dependency} and #{conflict}" }
|
203
|
+
end
|
204
|
+
|
205
|
+
def debug_cycle(manifest)
|
206
|
+
debug { "Cycle with #{manifest}" }
|
207
|
+
end
|
208
|
+
|
209
|
+
def map_find(enum)
|
210
|
+
enum.each do |obj|
|
211
|
+
res = yield(obj)
|
212
|
+
res.nil? or return res
|
213
|
+
end
|
214
|
+
nil
|
215
|
+
end
|
216
|
+
|
217
|
+
def index_by(enum)
|
218
|
+
Hash[enum.map{|obj| [yield(obj), obj]}]
|
219
|
+
end
|
220
|
+
|
221
|
+
def scope
|
222
|
+
@level += 1
|
223
|
+
yield
|
224
|
+
ensure
|
225
|
+
@level -= 1
|
226
|
+
end
|
227
|
+
|
228
|
+
def debug
|
229
|
+
environment.logger.debug { ' ' * @level + yield }
|
230
|
+
end
|
231
|
+
|
232
|
+
def environment
|
233
|
+
resolver.environment
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'librarian/error'
|
2
|
+
require 'librarian/resolver/implementation'
|
3
|
+
require 'librarian/manifest_set'
|
4
|
+
require 'librarian/resolution'
|
5
|
+
|
6
|
+
module Librarian
|
7
|
+
class Resolver
|
8
|
+
|
9
|
+
attr_accessor :environment, :cyclic
|
10
|
+
private :environment=, :cyclic=
|
11
|
+
|
12
|
+
# Options:
|
13
|
+
# cyclic: truthy if the resolver should permit cyclic resolutions
|
14
|
+
def initialize(environment, options = { })
|
15
|
+
unrecognized_options = options.keys - [:cyclic]
|
16
|
+
unrecognized_options.empty? or raise Error,
|
17
|
+
"unrecognized options: #{unrecognized_options.join(", ")}"
|
18
|
+
self.environment = environment
|
19
|
+
self.cyclic = !!options[:cyclic]
|
20
|
+
end
|
21
|
+
|
22
|
+
def resolve(spec, partial_manifests = [])
|
23
|
+
manifests = implementation(spec).resolve(partial_manifests)
|
24
|
+
manifests or return
|
25
|
+
enforce_consistency!(spec.dependencies, manifests)
|
26
|
+
enforce_acyclicity!(manifests) unless cyclic
|
27
|
+
manifests = sort(manifests)
|
28
|
+
Resolution.new(spec.dependencies, manifests)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def implementation(spec)
|
34
|
+
Implementation.new(self, spec, :cyclic => cyclic)
|
35
|
+
end
|
36
|
+
|
37
|
+
def enforce_consistency!(dependencies, manifests)
|
38
|
+
manifest_set = ManifestSet.new(manifests)
|
39
|
+
return if manifest_set.in_compliance_with?(dependencies)
|
40
|
+
return if manifest_set.consistent?
|
41
|
+
|
42
|
+
debug { "Resolver Malfunctioned!" }
|
43
|
+
errors = []
|
44
|
+
dependencies.sort_by(&:name).each do |d|
|
45
|
+
m = manifests[d.name]
|
46
|
+
if !m
|
47
|
+
errors << ["Depends on #{d}", "Missing!"]
|
48
|
+
elsif !d.satisfied_by?(m)
|
49
|
+
errors << ["Depends on #{d}", "Found: #{m}"]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
unless errors.empty?
|
53
|
+
errors.each do |a, b|
|
54
|
+
debug { " #{a}" }
|
55
|
+
debug { " #{b}" }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
manifests.values.sort_by(&:name).each do |manifest|
|
59
|
+
errors = []
|
60
|
+
manifest.dependencies.sort_by(&:name).each do |d|
|
61
|
+
m = manifests[d.name]
|
62
|
+
if !m
|
63
|
+
errors << ["Depends on: #{d}", "Missing!"]
|
64
|
+
elsif !d.satisfied_by?(m)
|
65
|
+
errors << ["Depends on: #{d}", "Found: #{m}"]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
unless errors.empty?
|
69
|
+
debug { " #{manifest}" }
|
70
|
+
errors.each do |a, b|
|
71
|
+
debug { " #{a}" }
|
72
|
+
debug { " #{b}" }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
raise Error, "Resolver Malfunctioned!"
|
77
|
+
end
|
78
|
+
|
79
|
+
def enforce_acyclicity!(manifests)
|
80
|
+
ManifestSet.cyclic?(manifests) or return
|
81
|
+
debug { "Resolver Malfunctioned!" }
|
82
|
+
raise Error, "Resolver Malfunctioned!"
|
83
|
+
end
|
84
|
+
|
85
|
+
def sort(manifests)
|
86
|
+
ManifestSet.sort(manifests)
|
87
|
+
end
|
88
|
+
|
89
|
+
def debug(*args, &block)
|
90
|
+
environment.logger.debug(*args, &block)
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require "json"
|
2
|
+
require "pathname"
|
3
|
+
require "securerandom"
|
4
|
+
require "stringio"
|
5
|
+
require "thor"
|
6
|
+
|
7
|
+
require "librarian/helpers"
|
8
|
+
|
9
|
+
module Librarian
|
10
|
+
module RSpec
|
11
|
+
module Support
|
12
|
+
module CliMacro
|
13
|
+
|
14
|
+
class FakeShell < Thor::Shell::Basic
|
15
|
+
def stdout
|
16
|
+
@stdout ||= StringIO.new
|
17
|
+
end
|
18
|
+
def stderr
|
19
|
+
@stderr ||= StringIO.new
|
20
|
+
end
|
21
|
+
def stdin
|
22
|
+
raise "unsupported"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class FileMatcher
|
27
|
+
attr_accessor :rel_path, :content, :type, :base_path
|
28
|
+
def initialize(rel_path, content, options = { })
|
29
|
+
self.rel_path = rel_path
|
30
|
+
self.content = content
|
31
|
+
self.type = options[:type]
|
32
|
+
end
|
33
|
+
def full_path
|
34
|
+
@full_path ||= base_path + rel_path
|
35
|
+
end
|
36
|
+
def actual_content
|
37
|
+
@actual_content ||= begin
|
38
|
+
s = full_path.read
|
39
|
+
s = JSON.parse(s) if type == :json
|
40
|
+
s
|
41
|
+
end
|
42
|
+
end
|
43
|
+
def matches?(base_path)
|
44
|
+
base_path = Pathname(base_path) unless Pathname === base_path
|
45
|
+
self.base_path = base_path
|
46
|
+
|
47
|
+
full_path.file? && (!content || actual_content == content)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.included(base)
|
52
|
+
base.instance_exec do
|
53
|
+
let(:project_path) do
|
54
|
+
project_path = Pathname.new(__FILE__).expand_path
|
55
|
+
project_path = project_path.dirname until project_path.join("Rakefile").exist?
|
56
|
+
project_path
|
57
|
+
end
|
58
|
+
let(:tmp) { project_path.join("tmp/spec/cli") }
|
59
|
+
let(:pwd) { tmp + SecureRandom.hex(8) }
|
60
|
+
|
61
|
+
before { tmp.mkpath }
|
62
|
+
before { pwd.mkpath }
|
63
|
+
|
64
|
+
after { tmp.rmtree }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def cli!(*args)
|
69
|
+
@shell = FakeShell.new
|
70
|
+
@exit_status = Dir.chdir(pwd) do
|
71
|
+
described_class.with_environment do
|
72
|
+
described_class.returning_status do
|
73
|
+
described_class.start args, :shell => @shell
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def write_file!(path, content)
|
80
|
+
path = pwd.join(path)
|
81
|
+
path.dirname.mkpath
|
82
|
+
path.open("wb"){|f| f.write(content)}
|
83
|
+
end
|
84
|
+
|
85
|
+
def write_json_file!(path, content)
|
86
|
+
write_file! path, JSON.dump(content)
|
87
|
+
end
|
88
|
+
|
89
|
+
def strip_heredoc(text)
|
90
|
+
Librarian::Helpers.strip_heredoc(text)
|
91
|
+
end
|
92
|
+
|
93
|
+
def shell
|
94
|
+
@shell
|
95
|
+
end
|
96
|
+
|
97
|
+
def stdout
|
98
|
+
shell.stdout.string
|
99
|
+
end
|
100
|
+
|
101
|
+
def stderr
|
102
|
+
shell.stderr.string
|
103
|
+
end
|
104
|
+
|
105
|
+
def exit_status
|
106
|
+
@exit_status
|
107
|
+
end
|
108
|
+
|
109
|
+
def have_file(rel_path, content = nil)
|
110
|
+
FileMatcher.new(rel_path, content)
|
111
|
+
end
|
112
|
+
|
113
|
+
def have_json_file(rel_path, content)
|
114
|
+
FileMatcher.new(rel_path, content, :type => :json)
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Librarian
|
2
|
+
module Source
|
3
|
+
module BasicApi
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend ClassMethods
|
7
|
+
class << base
|
8
|
+
def lock_name(name)
|
9
|
+
def_sclass_prop(:lock_name, name)
|
10
|
+
end
|
11
|
+
|
12
|
+
def spec_options(keys)
|
13
|
+
def_sclass_prop(:spec_options, keys)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def def_sclass_prop(name, arg)
|
19
|
+
sclass = class << self ; self ; end
|
20
|
+
sclass.module_exec do
|
21
|
+
remove_method(name)
|
22
|
+
define_method(name) { arg }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module ClassMethods
|
29
|
+
def from_lock_options(environment, options)
|
30
|
+
new(environment, options[:remote], options.reject{|k, v| k == :remote})
|
31
|
+
end
|
32
|
+
|
33
|
+
def from_spec_args(environment, param, options)
|
34
|
+
recognized_options = spec_options
|
35
|
+
unrecognized_options = options.keys - recognized_options
|
36
|
+
unrecognized_options.empty? or raise Error,
|
37
|
+
"unrecognized options: #{unrecognized_options.join(", ")}"
|
38
|
+
|
39
|
+
new(environment, param, options)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|