ki-repo 0.1.0 → 0.1.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.
@@ -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