ki-repo 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +26 -12
- data/VERSION +1 -1
- data/bin/ki +21 -0
- data/docs/backlog.md +35 -0
- data/docs/development.md +45 -0
- data/docs/development_setup.md +49 -0
- data/{lib/ki-repo.rb → docs/images/for_git.txt} +0 -0
- data/docs/ki_commands.md +202 -0
- data/docs/repository_basics.md +171 -0
- data/docs/writing_extensions.md +50 -0
- data/lib/cmd/cmd.rb +224 -0
- data/lib/cmd/user_pref_cmd.rb +122 -0
- data/lib/cmd/version_cmd.rb +483 -0
- data/lib/data_access/repository_finder.rb +200 -0
- data/lib/data_access/repository_info.rb +153 -0
- data/lib/data_access/version_helpers.rb +242 -0
- data/lib/data_access/version_iterators.rb +145 -0
- data/lib/data_access/version_operations.rb +80 -0
- data/lib/data_storage/dir_base.rb +106 -0
- data/lib/data_storage/ki_home.rb +44 -0
- data/lib/data_storage/ki_json.rb +153 -0
- data/lib/data_storage/repository.rb +91 -0
- data/lib/data_storage/version_metadata.rb +141 -0
- data/lib/ki_repo_all.rb +42 -0
- data/lib/util/attr_chain.rb +258 -0
- data/lib/util/exception_catcher.rb +118 -0
- data/lib/util/hash.rb +46 -0
- data/lib/util/hash_cache.rb +31 -0
- data/lib/util/ruby_extensions.rb +137 -0
- data/lib/util/service_registry.rb +88 -0
- data/lib/util/simple_optparse.rb +103 -0
- data/lib/util/test.rb +323 -0
- metadata +69 -13
- data/.document +0 -5
- data/.rspec +0 -1
- data/Gemfile +0 -14
- data/Gemfile.lock +0 -38
- data/Rakefile +0 -42
- data/spec/ki-repo_spec.rb +0 -6
- data/spec/spec_helper.rb +0 -12
@@ -0,0 +1,483 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Copyright 2012 Mikko Apo
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
module Ki
|
18
|
+
|
19
|
+
# Builds and updates ki-metada.json file based on parameters and added files
|
20
|
+
# @see VersionMetadataFile
|
21
|
+
class BuildVersionMetadataFile
|
22
|
+
attr_chain :input_dir, -> { Dir.pwd }
|
23
|
+
attr_chain :metadata_file, -> { VersionMetadataFile.new("ki-version.json") }
|
24
|
+
attr_chain :source_parameters, -> { Hash.new }
|
25
|
+
attr_chain :default_parameters, -> { {"hashes" => ["sha1"], "tags" => []} }
|
26
|
+
attr_chain :previous_dep, :require => "Define a dependency before -o or --operation"
|
27
|
+
attr_chain :shell_command, :require
|
28
|
+
|
29
|
+
def execute(ctx, args)
|
30
|
+
# opts.parse parses input parameters and fills in configuration parameters
|
31
|
+
files = opts.parse(args)
|
32
|
+
if source_parameters.size > 0
|
33
|
+
metadata_file.source(source_parameters)
|
34
|
+
end
|
35
|
+
# adds files to metadata and fills in parameters
|
36
|
+
metadata_file.add_files(input_dir, files, default_parameters)
|
37
|
+
metadata_file.save
|
38
|
+
end
|
39
|
+
|
40
|
+
def help
|
41
|
+
<<EOF
|
42
|
+
"#{shell_command}" can be used to generate version metadata files. Version metadata files
|
43
|
+
contain information about files (size, permission bits, hash checksums), version origins
|
44
|
+
and dependencies.
|
45
|
+
|
46
|
+
After version metadata file is ready, it can be imported to repository using version-import.
|
47
|
+
|
48
|
+
### Usage
|
49
|
+
|
50
|
+
#{shell_command} <parameters> file_pattern1*.* file_pattern2*.*
|
51
|
+
|
52
|
+
### Examples
|
53
|
+
|
54
|
+
#{shell_command} test.sh
|
55
|
+
#{shell_command} readme* -t doc
|
56
|
+
#{shell_command} -d my/component/1,name=comp,path=doc,internal -O "mv doc/test.sh helloworld.sh"
|
57
|
+
ki version-import
|
58
|
+
|
59
|
+
### Parameters
|
60
|
+
#{opts}
|
61
|
+
EOF
|
62
|
+
end
|
63
|
+
|
64
|
+
def summary
|
65
|
+
"Create version metadata file"
|
66
|
+
end
|
67
|
+
|
68
|
+
def opts
|
69
|
+
OptionParser.new do |opts|
|
70
|
+
opts.banner = ""
|
71
|
+
opts.on("-f", "--file FILE", "Version file target") do |v|
|
72
|
+
if !defined? @input_dir
|
73
|
+
input_dir(File.dirname(v))
|
74
|
+
end
|
75
|
+
metadata_file.init_from_path(v)
|
76
|
+
end
|
77
|
+
opts.on("-i", "--input-directory INPUT-DIR", "Input directory") do |v|
|
78
|
+
input_dir(v)
|
79
|
+
end
|
80
|
+
opts.on("-v", "--version-id VERSION-ID", "Version's id") do |v|
|
81
|
+
metadata_file.version_id=v
|
82
|
+
end
|
83
|
+
["url", "tag-url", "author", "repotype"].each do |source_param|
|
84
|
+
opts.on("--source-#{source_param} #{source_param.upcase}", "Build source parameter #{source_param}") do |v|
|
85
|
+
source_parameters[source_param]=v
|
86
|
+
end
|
87
|
+
end
|
88
|
+
opts.on("-t", "--tags TAGS", "Tag files with keywords") do |v|
|
89
|
+
default_parameters["tags"]= v.split(",").sort
|
90
|
+
end
|
91
|
+
hash_prefix = "/hashing"
|
92
|
+
hashes = KiCommand::KiExtensions.find(hash_prefix).map { |k, v| k[hash_prefix.size+1..-1] }
|
93
|
+
opts.on("--hashes HASHES", "Calculate checksums using defined hash algos. Default: sha1. Available: #{hashes.join(", ")}") do |v|
|
94
|
+
default_parameters["hashes"]= v.split(",").sort
|
95
|
+
end
|
96
|
+
opts.on("-d", "--dependency DEPENDENCY", "Dependency definition my/component/123[,name=AA][,path=aa][,internal]") do |v|
|
97
|
+
previous_dep(metadata_file.add_dependency(v))
|
98
|
+
end
|
99
|
+
opts.on("-o", "--operation OP", "Add operation to previous dependency") do |v|
|
100
|
+
previous_dep.add_operation(v.split(" "))
|
101
|
+
end
|
102
|
+
opts.on("-O", "--version-operation OP", "Add operation to version") do |v|
|
103
|
+
metadata_file.add_operation(v.split(" "))
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Tests version from repository or metadata file
|
110
|
+
# @see VersionTester
|
111
|
+
class TestVersion
|
112
|
+
attr_chain :shell_command, :require
|
113
|
+
|
114
|
+
def execute(ctx, args)
|
115
|
+
@tester = VersionTester.new.recursive(false).print(true)
|
116
|
+
ver_strs = opts.parse(args)
|
117
|
+
if ver_strs.size > 0 || @tester.recursive
|
118
|
+
@tester.ki_home(ctx.ki_home)
|
119
|
+
versions = ver_strs.map { |v| ctx.ki_home.version(v) }
|
120
|
+
else
|
121
|
+
versions = []
|
122
|
+
end
|
123
|
+
if @file
|
124
|
+
versions.unshift Version.create_version(@file, @input_dir)
|
125
|
+
end
|
126
|
+
all_ok = true
|
127
|
+
versions.each do |v|
|
128
|
+
all_ok = all_ok && @tester.test_version(v)
|
129
|
+
end
|
130
|
+
if all_ok
|
131
|
+
puts "All files ok."
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def help
|
136
|
+
<<EOF
|
137
|
+
"#{shell_command}" tests versions, their files and their dependencies. Can also test version that has not been imported yet.
|
138
|
+
|
139
|
+
### Examples
|
140
|
+
|
141
|
+
#{shell_command} -r my/product other/product
|
142
|
+
#{shell_command} -f ki-version.json -i file-directory
|
143
|
+
|
144
|
+
### Parameters
|
145
|
+
#{opts}
|
146
|
+
EOF
|
147
|
+
end
|
148
|
+
|
149
|
+
def summary
|
150
|
+
"Tests versions and their dependencies"
|
151
|
+
end
|
152
|
+
|
153
|
+
def opts
|
154
|
+
OptionParser.new do |opts|
|
155
|
+
opts.banner = ""
|
156
|
+
opts.on("-f", "--file FILE", "Version source file. By default uses file's directory as source for binary files.'") do |v|
|
157
|
+
if @input_dir.nil?
|
158
|
+
dir = File.dirname(v)
|
159
|
+
@input_dir = dir != "." ? dir : Dir.pwd
|
160
|
+
end
|
161
|
+
@file = v
|
162
|
+
end
|
163
|
+
opts.on("-i", "--input-directory INPUT-DIR", "Binary file input directory") do |v|
|
164
|
+
@input_dir = v
|
165
|
+
end
|
166
|
+
opts.on("-r", "--recursive", "Tests version's dependencies also.'") do |v|
|
167
|
+
@tester.recursive = true
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Imports version and its files to repository
|
174
|
+
# @see VersionImporter
|
175
|
+
class ImportVersion
|
176
|
+
attr_chain :input_dir, -> { Dir.pwd }
|
177
|
+
attr_chain :file, -> { File.join(input_dir, "ki-version.json") }
|
178
|
+
attr_chain :importer, -> {}
|
179
|
+
attr_chain :shell_command, :require
|
180
|
+
|
181
|
+
def execute(ctx, args)
|
182
|
+
@importer = VersionImporter.new
|
183
|
+
opts.parse(args)
|
184
|
+
@importer.ki_home(ctx.ki_home).import(file, input_dir)
|
185
|
+
end
|
186
|
+
|
187
|
+
def help
|
188
|
+
<<EOF
|
189
|
+
"#{shell_command}" imports version and its files to repository.
|
190
|
+
|
191
|
+
Version name can be defined either during "version-build",
|
192
|
+
or generated automatically for component at import (with -c my/component) or defined to be a specific version (-v).
|
193
|
+
Can also move files (-m), test dependencies before import (-t).
|
194
|
+
|
195
|
+
### Examples
|
196
|
+
|
197
|
+
#{shell_command} -m -t -c my/product
|
198
|
+
#{shell_command} -f ki-version.json -i file-directory
|
199
|
+
|
200
|
+
### Parameters
|
201
|
+
#{opts}
|
202
|
+
EOF
|
203
|
+
end
|
204
|
+
|
205
|
+
def summary
|
206
|
+
"Imports version metadata and files to repository"
|
207
|
+
end
|
208
|
+
|
209
|
+
def opts
|
210
|
+
OptionParser.new do |opts|
|
211
|
+
opts.banner = ""
|
212
|
+
opts.on("-f", "--file FILE", "Version source file. By default uses file's directory as source for binary files.'") do |v|
|
213
|
+
if !defined? @input_dir
|
214
|
+
input_dir(File.dirname(v))
|
215
|
+
end
|
216
|
+
file(v)
|
217
|
+
end
|
218
|
+
opts.on("-i", "--input-directory INPUT-DIR", "Input directory") do |v|
|
219
|
+
input_dir(v)
|
220
|
+
end
|
221
|
+
opts.on("-t", "--test-recursive", "Tests version's dependencies before importing.'") do |v|
|
222
|
+
@importer.tester.recursive = true
|
223
|
+
end
|
224
|
+
opts.on("-m", "--move", "Moves files to repository'") do |v|
|
225
|
+
@importer.move_files = true
|
226
|
+
end
|
227
|
+
opts.on("-c", "--create-new-version COMPONENT", "Creates new version number for defined component'") do |c|
|
228
|
+
@importer.create_new_version = c
|
229
|
+
end
|
230
|
+
opts.on("-v", "--version-id VERSION", "Imports version with defined version id'") do |v|
|
231
|
+
@importer.specific_version_id = v
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# Exports version from repository to target directory
|
238
|
+
# @see VersionExporter
|
239
|
+
class ExportVersion
|
240
|
+
attr_chain :out, -> { Dir.pwd }
|
241
|
+
attr_chain :shell_command, :require
|
242
|
+
|
243
|
+
def execute(ctx, args)
|
244
|
+
@exporter = VersionExporter.new
|
245
|
+
file_patterns = opts.parse(args)
|
246
|
+
version = file_patterns.delete_at(0)
|
247
|
+
@exporter.find_files.files(file_patterns)
|
248
|
+
@exporter.ki_home(ctx.ki_home).export(version, out)
|
249
|
+
end
|
250
|
+
|
251
|
+
def help
|
252
|
+
<<EOF
|
253
|
+
"#{shell_command}" exports version and its dependencies to target directory.
|
254
|
+
|
255
|
+
### Usage
|
256
|
+
|
257
|
+
#{shell_command} <parameters> <file_export_pattern*.*>
|
258
|
+
|
259
|
+
### Examples
|
260
|
+
|
261
|
+
#{shell_command} -o export-dir --tags -c bin my/product
|
262
|
+
#{shell_command} -o scripts -c -t my/admin-tools '*.sh'
|
263
|
+
|
264
|
+
### Parameters
|
265
|
+
#{opts}
|
266
|
+
EOF
|
267
|
+
end
|
268
|
+
|
269
|
+
def summary
|
270
|
+
"Export version to a directory"
|
271
|
+
end
|
272
|
+
|
273
|
+
def opts
|
274
|
+
OptionParser.new do |opts|
|
275
|
+
opts.banner = ""
|
276
|
+
opts.on("-o", "--output-directory INPUT-DIR", "Input directory") do |v|
|
277
|
+
out(v)
|
278
|
+
end
|
279
|
+
opts.on("--tags TAGS", "Select files with matching tag") do |v|
|
280
|
+
@exporter.find_files.tags(v.split(","))
|
281
|
+
end
|
282
|
+
opts.on("-t", "--test", "Test version before export") do |v|
|
283
|
+
@exporter.test_dependencies=true
|
284
|
+
end
|
285
|
+
opts.on("-c", "--copy", "Exported files are copied instead of linked") do |v|
|
286
|
+
@exporter.copy=true
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
# Sets status for version
|
293
|
+
class VersionStatus
|
294
|
+
attr_chain :shell_command, :require
|
295
|
+
|
296
|
+
def execute(ctx, args)
|
297
|
+
@repository = "local"
|
298
|
+
command = args.delete_at(0)
|
299
|
+
case command
|
300
|
+
when "add"
|
301
|
+
version, key_value, *rest = args
|
302
|
+
key, value = key_value.split("=")
|
303
|
+
flags = rest.to_h("=")
|
304
|
+
repository = ctx.ki_home.repository(@repository)
|
305
|
+
repository.version(version).statuses.add_status(key, value, flags)
|
306
|
+
when "order"
|
307
|
+
component, key, values_str = args
|
308
|
+
repository = ctx.ki_home.repository(@repository)
|
309
|
+
repository.component(component).status_info.edit_data do |info|
|
310
|
+
info.cached_data[key]=values_str.split(",")
|
311
|
+
end
|
312
|
+
else
|
313
|
+
raise "Not supported '#{command}'"
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
def help
|
318
|
+
<<EOF
|
319
|
+
"#{shell_command}" sets status values to versions and sets status value order to component.
|
320
|
+
|
321
|
+
Status value order is used to determine which statuses match version queries:
|
322
|
+
|
323
|
+
my/component:maturity>alpha
|
324
|
+
|
325
|
+
### Examples
|
326
|
+
|
327
|
+
#{shell_command} add my/component/1.2.3 Smoke=Green action=path/123
|
328
|
+
#{shell_command} order my/component maturity alpha,beta,gamma
|
329
|
+
EOF
|
330
|
+
end
|
331
|
+
|
332
|
+
def summary
|
333
|
+
"Add status values to version"
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
# Shows information about a version
|
338
|
+
class ShowVersion
|
339
|
+
attr_chain :shell_command, :require
|
340
|
+
|
341
|
+
def execute(ctx, args)
|
342
|
+
finder = ctx.ki_home.finder
|
343
|
+
versions = opts.parse(args).map { |v| finder.version(v) }
|
344
|
+
if @file
|
345
|
+
versions.unshift Version.create_version(@file, @input_dir)
|
346
|
+
end
|
347
|
+
versions.each do |ver|
|
348
|
+
VersionIterator.new.finder(finder).version(ver).iterate_versions do |version|
|
349
|
+
metadata = version.metadata
|
350
|
+
puts "Version: #{metadata.version_id}"
|
351
|
+
if metadata.source.size > 0
|
352
|
+
puts "Source: #{map_to_csl(metadata.source)}"
|
353
|
+
end
|
354
|
+
if metadata.dependencies.size > 0
|
355
|
+
puts "Dependencies(#{metadata.dependencies.size}):"
|
356
|
+
metadata.dependencies.each do |dep|
|
357
|
+
dep_data = dep.dup
|
358
|
+
dep_ops = dep_data.delete("operations")
|
359
|
+
puts "#{dep_data.delete("version_id")}: #{map_to_csl(dep_data)}"
|
360
|
+
if dep_ops && dep_ops.size > 0
|
361
|
+
puts "Depedency operations:"
|
362
|
+
dep_ops.each do |op|
|
363
|
+
puts op.join(" ")
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
368
|
+
if metadata.files.size > 0
|
369
|
+
puts "Files(#{metadata.files.size}):"
|
370
|
+
metadata.files.each do |file|
|
371
|
+
file_data = file.dup
|
372
|
+
puts "#{file_data.delete("path")} - size: #{file_data.delete("size")}, #{map_to_csl(file_data)}"
|
373
|
+
end
|
374
|
+
end
|
375
|
+
if metadata.operations.size > 0
|
376
|
+
puts "Version operations(#{metadata.operations.size}):"
|
377
|
+
metadata.operations.each do |op|
|
378
|
+
puts op.join(" ")
|
379
|
+
end
|
380
|
+
end
|
381
|
+
if @dirs
|
382
|
+
puts "Version directories: #{version.versions.map { |v| v.path }.join(", ")}"
|
383
|
+
end
|
384
|
+
if !@recursive
|
385
|
+
break
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
def map_to_csl(map)
|
392
|
+
map.sort.map { |k, v| "#{k}=#{Array.wrap(v).join(",")}" }.join(", ")
|
393
|
+
end
|
394
|
+
|
395
|
+
def help
|
396
|
+
<<EOF
|
397
|
+
"#{shell_command}" prints information about version or versions and their dependencies
|
398
|
+
|
399
|
+
### Examples
|
400
|
+
|
401
|
+
#{shell_command} -r -d my/component/23 my/product/127
|
402
|
+
#{shell_command} -f ki-version.json -i binary-dir
|
403
|
+
EOF
|
404
|
+
end
|
405
|
+
|
406
|
+
def summary
|
407
|
+
"Prints information about version or versions"
|
408
|
+
end
|
409
|
+
|
410
|
+
def opts
|
411
|
+
OptionParser.new do |opts|
|
412
|
+
opts.banner = ""
|
413
|
+
opts.on("-r", "--recursive", "Shows version's dependencies.") do |v|
|
414
|
+
@recursive = true
|
415
|
+
end
|
416
|
+
opts.on("-d", "--dirs", "Shows version's directories.") do |v|
|
417
|
+
@dirs = true
|
418
|
+
end
|
419
|
+
opts.on("-f", "--file FILE", "Version source file. By default uses file's directory as source for binary files.") do |v|
|
420
|
+
if @input_dir.nil?
|
421
|
+
dir = File.dirname(v)
|
422
|
+
@input_dir = dir != "." ? dir : Dir.pwd
|
423
|
+
end
|
424
|
+
@file = v
|
425
|
+
end
|
426
|
+
opts.on("-i", "--input-directory INPUT-DIR", "Binary file input directory") do |v|
|
427
|
+
@input_dir = v
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
# Sets status for version
|
434
|
+
class VersionSearch
|
435
|
+
attr_chain :shell_command, :require
|
436
|
+
|
437
|
+
def execute(ctx, args)
|
438
|
+
finder = ctx.ki_home.finder
|
439
|
+
args.each do |arg|
|
440
|
+
version = finder.version(arg)
|
441
|
+
if version
|
442
|
+
puts version.version_id
|
443
|
+
else
|
444
|
+
matcher = FileRegexp.matcher(arg)
|
445
|
+
found_components = finder.components.keys.select { |name| matcher.match(name) }
|
446
|
+
if found_components.size > 0
|
447
|
+
puts "Found components(#{found_components.size}):"
|
448
|
+
puts found_components.join("\n")
|
449
|
+
else
|
450
|
+
puts "'#{arg}' does not match versions or components"
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
def help
|
457
|
+
<<EOF
|
458
|
+
"#{shell_command}" searches for versions and components.
|
459
|
+
|
460
|
+
### Examples
|
461
|
+
|
462
|
+
#{shell_command} my/component
|
463
|
+
#{shell_command} my/*
|
464
|
+
EOF
|
465
|
+
end
|
466
|
+
|
467
|
+
def summary
|
468
|
+
"Searches for versions and components"
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
KiCommand.register_cmd("version-build", BuildVersionMetadataFile)
|
473
|
+
KiCommand.register_cmd("version-test", TestVersion)
|
474
|
+
KiCommand.register_cmd("version-import", ImportVersion)
|
475
|
+
KiCommand.register_cmd("version-export", ExportVersion)
|
476
|
+
KiCommand.register_cmd("version-status", VersionStatus)
|
477
|
+
KiCommand.register_cmd("version-show", ShowVersion)
|
478
|
+
KiCommand.register_cmd("version-search", VersionSearch)
|
479
|
+
KiCommand.register("/hashing/sha1", SHA1)
|
480
|
+
KiCommand.register("/hashing/sha2", SHA2)
|
481
|
+
KiCommand.register("/hashing/md5", MD5)
|
482
|
+
|
483
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Copyright 2012 Mikko Apo
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
module Ki
|
18
|
+
# Collects components from multiple repositories and provides methods to find components and versions
|
19
|
+
# @see Component
|
20
|
+
# @see Version
|
21
|
+
class RepositoryFinder
|
22
|
+
attr_reader :versions
|
23
|
+
attr_reader :components
|
24
|
+
|
25
|
+
def initialize(source)
|
26
|
+
@source = source
|
27
|
+
@components = load_all_components
|
28
|
+
@versions = HashCache.new
|
29
|
+
end
|
30
|
+
|
31
|
+
# Finds matching component by name
|
32
|
+
# @return [Component] matching component
|
33
|
+
def component(component)
|
34
|
+
@components[component]
|
35
|
+
end
|
36
|
+
|
37
|
+
# Finds version matching arguments, goes through versions in chronological order from latest to oldest.
|
38
|
+
# * note: block form can be used to iterate through all available version
|
39
|
+
# Supported search formats:
|
40
|
+
# * version("test/comp") -> latest version
|
41
|
+
# * version("test/comp:Smoke=green"), version("test/comp","Smoke"=>"green") -> latest version with matching status
|
42
|
+
# * version("test/comp"){|version| ... } -> iterates all available versions and returns latest version where block returns true, block argument is a Version
|
43
|
+
# * version("test/comp:maturity!=alpha") ->
|
44
|
+
# Component can define ordering for status values: {"maturity": ["alpha","beta","gamma"]}
|
45
|
+
# * version("test/comp:maturity>alpha") -> returns first version where maturity is beta or gamma
|
46
|
+
# * version("test/comp:maturity>=alpha") -> returns first version where maturity is alpha, beta or gamma
|
47
|
+
# * version("test/comp:maturity<beta") -> returns first version where maturity is alpha
|
48
|
+
# * version("test/comp:maturity<=beta") -> returns first version where maturity is alpha or beta
|
49
|
+
# Version supports also Component and Version parameters:
|
50
|
+
# * version(my_version) -> returns my_version
|
51
|
+
# * version(my_component, "Smoke:green") -> finds Version matching other parameters
|
52
|
+
# @return [Version] matching version
|
53
|
+
def version(*args, &block)
|
54
|
+
if args.empty?
|
55
|
+
raise "no parameters!"
|
56
|
+
end
|
57
|
+
status_rules = []
|
58
|
+
component_or_version = nil
|
59
|
+
dep_navigation_arr = []
|
60
|
+
args.each do |str|
|
61
|
+
if str.kind_of?(String)
|
62
|
+
dep_nav_arr = str.split("->")
|
63
|
+
strings = dep_nav_arr.delete_at(0).split(":")
|
64
|
+
dep_navigation_arr.concat(dep_nav_arr)
|
65
|
+
if component_or_version.nil?
|
66
|
+
component_or_version = strings.delete_at(0)
|
67
|
+
end
|
68
|
+
strings.each do |s|
|
69
|
+
status_rules << s.match(/(.*?)([<=>!]+)(.*)/).captures
|
70
|
+
end
|
71
|
+
elsif str.kind_of?(Hash)
|
72
|
+
str.each_pair do |k, v|
|
73
|
+
status_rules << [k, "=", v]
|
74
|
+
end
|
75
|
+
elsif str.kind_of?(Version)
|
76
|
+
return str
|
77
|
+
elsif str.kind_of?(Component) && component_or_version.nil?
|
78
|
+
component_or_version = str.component_id
|
79
|
+
else
|
80
|
+
raise "Not supported '#{str.inspect}'"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
if component = @components[component_or_version]
|
84
|
+
if status_rules.size > 0 || block
|
85
|
+
component.versions.each do |v|
|
86
|
+
ver = component.version_by_id(v.name)
|
87
|
+
ok = has_statuses(ver.statuses, status_rules, component)
|
88
|
+
if ok && block
|
89
|
+
ok = block.call(ver)
|
90
|
+
end
|
91
|
+
if ok
|
92
|
+
return ver
|
93
|
+
end
|
94
|
+
end
|
95
|
+
else
|
96
|
+
# picking latest version
|
97
|
+
version_name = component.versions.first.name
|
98
|
+
end
|
99
|
+
else
|
100
|
+
# user has defined an exact version
|
101
|
+
version_arr = component_or_version.split("/")
|
102
|
+
version_name = version_arr.delete_at(-1)
|
103
|
+
component_str = version_arr.join("/")
|
104
|
+
component = @components[component_str]
|
105
|
+
end
|
106
|
+
if component && version_name
|
107
|
+
ver = component.version_by_id(version_name)
|
108
|
+
if dep_navigation_arr
|
109
|
+
dep_navigation_arr.each do |dep_str|
|
110
|
+
dep_version_str = find_dep_by_name(ver, dep_str)
|
111
|
+
if dep_version_str.nil?
|
112
|
+
raise "Could not locate dependency '#{dep_str}' from '#{ver.version_id}'"
|
113
|
+
end
|
114
|
+
ver = version(dep_version_str)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
ver
|
118
|
+
else
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def find_dep_by_name(ver, dep_str)
|
124
|
+
ver.metadata.dependencies.each do |dep|
|
125
|
+
if dep["name"] == dep_str
|
126
|
+
return dep["version_id"]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
nil
|
130
|
+
end
|
131
|
+
|
132
|
+
def all_repositories(source=@source)
|
133
|
+
node = source
|
134
|
+
repositories = []
|
135
|
+
while (node)
|
136
|
+
repositories.concat(node.repositories.to_a)
|
137
|
+
if node.root?
|
138
|
+
break
|
139
|
+
end
|
140
|
+
node = node.parent
|
141
|
+
end
|
142
|
+
repositories
|
143
|
+
end
|
144
|
+
|
145
|
+
# Loads all Component from all repositories
|
146
|
+
# @param [KiHome] source
|
147
|
+
def load_all_components(source=@source)
|
148
|
+
components = HashCache.new
|
149
|
+
all_repositories(source).each do |info|
|
150
|
+
info.components.each do |component_info|
|
151
|
+
component = components.cache(component_info.component_id) do
|
152
|
+
Component.new.component_id(component_info.component_id).finder(self).components([])
|
153
|
+
end
|
154
|
+
component.components << component_info
|
155
|
+
end
|
156
|
+
end
|
157
|
+
components
|
158
|
+
end
|
159
|
+
|
160
|
+
# locates first matching status for the key and checks if that is ok for the block
|
161
|
+
def check_status_value(version_statuses, key, &block)
|
162
|
+
version_statuses.each do |status_key, status_value|
|
163
|
+
if status_key == key
|
164
|
+
return block.call(status_value)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
false
|
168
|
+
end
|
169
|
+
|
170
|
+
# Checks if version's statuses match status_rules
|
171
|
+
def has_statuses(version_statuses_original, status_rules, component)
|
172
|
+
ret = true
|
173
|
+
if status_rules.size > 0
|
174
|
+
ret = false
|
175
|
+
version_statuses = version_statuses_original.reverse # latest first
|
176
|
+
status_info = component.status_info
|
177
|
+
# go through each rule and see if this version has matching status
|
178
|
+
status_rules.each do |key, op, value|
|
179
|
+
if order = status_info[key]
|
180
|
+
rule_value_index = order.index(value)
|
181
|
+
end
|
182
|
+
op_action = {
|
183
|
+
"=" => ->(status_value) { status_value == value },
|
184
|
+
"!=" => ->(status_value) { status_value != value },
|
185
|
+
"<" => ->(status_value) { order.index(status_value) < rule_value_index },
|
186
|
+
">" => ->(status_value) { order.index(status_value) > rule_value_index },
|
187
|
+
">=" => ->(status_value) { order.index(status_value) >= rule_value_index },
|
188
|
+
"<=" => ->(status_value) { order.index(status_value) <= rule_value_index }
|
189
|
+
}.fetch(op) do
|
190
|
+
raise "Not supported status operation: '#{key}#{op}#{value}'"
|
191
|
+
end
|
192
|
+
ret = check_status_value(version_statuses, key) do |status_value|
|
193
|
+
op_action.call(status_value)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
ret
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|