hammer_cli 0.19.2 → 2.0.0

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/bin/hammer-complete +28 -0
  3. data/config/cli_config.template.yml +2 -0
  4. data/config/hammer.completion +5 -0
  5. data/doc/creating_commands.md +27 -0
  6. data/doc/installation.md +47 -4
  7. data/doc/release_notes.md +17 -9
  8. data/lib/hammer_cli.rb +1 -0
  9. data/lib/hammer_cli/abstract.rb +32 -2
  10. data/lib/hammer_cli/apipie/api_connection.rb +5 -1
  11. data/lib/hammer_cli/apipie/command.rb +3 -2
  12. data/lib/hammer_cli/bash.rb +2 -0
  13. data/lib/hammer_cli/bash/completion.rb +159 -0
  14. data/lib/hammer_cli/bash/prebuild_command.rb +21 -0
  15. data/lib/hammer_cli/connection.rb +4 -0
  16. data/lib/hammer_cli/exception_handler.rb +10 -1
  17. data/lib/hammer_cli/main.rb +5 -3
  18. data/lib/hammer_cli/options/normalizers.rb +7 -3
  19. data/lib/hammer_cli/options/option_definition.rb +22 -0
  20. data/lib/hammer_cli/output/adapter/abstract.rb +1 -5
  21. data/lib/hammer_cli/output/adapter/base.rb +1 -1
  22. data/lib/hammer_cli/output/adapter/csv.rb +3 -2
  23. data/lib/hammer_cli/output/adapter/json.rb +14 -3
  24. data/lib/hammer_cli/output/adapter/silent.rb +1 -1
  25. data/lib/hammer_cli/output/adapter/table.rb +26 -7
  26. data/lib/hammer_cli/output/adapter/yaml.rb +6 -3
  27. data/lib/hammer_cli/output/output.rb +2 -4
  28. data/lib/hammer_cli/settings.rb +2 -1
  29. data/lib/hammer_cli/utils.rb +5 -0
  30. data/lib/hammer_cli/version.rb +1 -1
  31. data/locale/ca/LC_MESSAGES/hammer-cli.mo +0 -0
  32. data/locale/de/LC_MESSAGES/hammer-cli.mo +0 -0
  33. data/locale/en/LC_MESSAGES/hammer-cli.mo +0 -0
  34. data/locale/en_GB/LC_MESSAGES/hammer-cli.mo +0 -0
  35. data/locale/es/LC_MESSAGES/hammer-cli.mo +0 -0
  36. data/locale/fr/LC_MESSAGES/hammer-cli.mo +0 -0
  37. data/locale/it/LC_MESSAGES/hammer-cli.mo +0 -0
  38. data/locale/ja/LC_MESSAGES/hammer-cli.mo +0 -0
  39. data/locale/ko/LC_MESSAGES/hammer-cli.mo +0 -0
  40. data/locale/pt_BR/LC_MESSAGES/hammer-cli.mo +0 -0
  41. data/locale/ru/LC_MESSAGES/hammer-cli.mo +0 -0
  42. data/locale/zh_CN/LC_MESSAGES/hammer-cli.mo +0 -0
  43. data/locale/zh_TW/LC_MESSAGES/hammer-cli.mo +0 -0
  44. data/man/hammer.1.gz +0 -0
  45. data/test/unit/apipie/api_connection_test.rb +1 -0
  46. data/test/unit/bash_test.rb +138 -0
  47. data/test/unit/exception_handler_test.rb +44 -0
  48. data/test/unit/output/adapter/base_test.rb +58 -0
  49. data/test/unit/output/adapter/csv_test.rb +63 -1
  50. data/test/unit/output/adapter/json_test.rb +61 -0
  51. data/test/unit/output/adapter/table_test.rb +70 -1
  52. data/test/unit/output/adapter/yaml_test.rb +59 -0
  53. data/test/unit/output/output_test.rb +3 -3
  54. metadata +75 -68
  55. data/hammer_cli_complete +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da086330c730e02850d33b001aecfc8203c366f535b03c9c48c33f83811fc796
4
- data.tar.gz: 912ec4c67dbefe2cb3d6595cc6a8f8b484f12d4c004dd901442135fb4cce9103
3
+ metadata.gz: 1beb690641cf038528a35bee7964c73e60acbe8a70e94bb9d1a72af372218ea1
4
+ data.tar.gz: 516fd63e88f8df2996aefd0d3276a9ff56e1bf5f17c1a7a8b656210512f2896c
5
5
  SHA512:
6
- metadata.gz: 655cd84a3f0114b2c216157941c86f7b76dcb7c79a54efcf8c7fe66ed72cc9bc83671eaef98223894e396c29d433df76f17ad86f980a92afdd6ec70591caffc8
7
- data.tar.gz: 3a1a1826637889a8bf6ee7d8db334944daa0c30b343282b7e29edf7463d84cdc90084a696aacec19f3ca4a0dde9083fe41d4801e88449bd3493d5af6d7ec6337
6
+ metadata.gz: 8b35646b4dc9c494687b7bbd38c578ce5cf70e90080025f622b89a28f1c1d29abeafc11dbe1e16cb0786fe8ae3ac4c8673b905e8bf03cf87de86383817c653a6
7
+ data.tar.gz: 3a01e546cf71e7f015ec27d8c127328e380c3736106bc01b95d1b81593f2c7fa0a027e90a95bbf179407a953da871911e8dd0349c0ed980ed5cad613bd98363b
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'English'
4
+ @project_root = File.expand_path('../..', __FILE__)
5
+ $LOAD_PATH.unshift(File.join(@project_root, 'lib'))
6
+ HAMMER = ENV['HAMMER'] || File.join(@project_root, 'bin/hammer')
7
+
8
+ require 'yaml'
9
+
10
+ completion_cache_file = ENV['HAMMER_COMPLETION_CACHE'] || '~/.cache/hammer_completion.yml'
11
+ completion_cache_file = File.expand_path(completion_cache_file)
12
+
13
+ # build the cache if it does not exist
14
+ unless File.exist?(completion_cache_file)
15
+ require 'hammer_cli'
16
+ `#{HAMMER} prebuild-bash-completion`
17
+ end
18
+
19
+ require 'hammer_cli/bash/completion'
20
+
21
+ dict = HammerCLI::Bash::Completion.load_description(completion_cache_file)
22
+
23
+ comp_line = ENV['COMP_LINE'] || ''
24
+ comp_args = comp_line.split(' ', 2).last || ''
25
+
26
+ result = HammerCLI::Bash::Completion.new(dict).complete(comp_args)
27
+
28
+ puts result.join("\n")
@@ -44,6 +44,8 @@
44
44
  # Log record pattern (logging gem syntax)
45
45
  #:log_pattern: '[%5l %d %c] %m'
46
46
 
47
+ # Location of cache for bash completion
48
+ :completion_cache_file: '~/.cache/hammer_completion.yml'
47
49
 
48
50
  # SSL auth options
49
51
  #:ssl:
@@ -0,0 +1,5 @@
1
+ #
2
+ # Hammer CLI bash completion script
3
+ #
4
+
5
+ complete -C hammer-complete -o nospace hammer
@@ -494,6 +494,33 @@ You first create an _output definition_ that you apply to your data. The result
494
494
  is a collection of fields, each having its type. The collection is then passed to an
495
495
  _output adapter_ which handles the actual formatting and printing.
496
496
 
497
+ Adapters support printing by chunks, e.g. if you want to print a large set of
498
+ data (1000+ records), but you make several calls to the server instead of one,
499
+ you may want to print received data right away instead of waiting for the rest.
500
+ This can be achieved via `:current_chunk` option for
501
+ `print_collection` and `print_data` methods. Allowed values for `:current_chunk`
502
+ are `:first`, `:another`, `:last`. By default adapters use `:single` value that
503
+ means only one record will be printed.
504
+
505
+ ##### Printing by chunks
506
+ ```ruby
507
+ # ...
508
+ def execute
509
+ loop do
510
+ # ...
511
+ data = send_request
512
+ print_data(data, current_chunk: :first)
513
+ # ...
514
+ data = send_request
515
+ print_data(data, current_chunk: :another)
516
+ # ...
517
+ data = send_request
518
+ print_data(data, current_chunk: :last)
519
+ end
520
+ end
521
+ # ...
522
+ ```
523
+
497
524
  Hammer provides a DSL for defining the output. Next rather complex example will
498
525
  explain how to use it in action.
499
526
 
data/doc/installation.md CHANGED
@@ -132,8 +132,51 @@ And you are done. Your hammer client is configured and ready to use.
132
132
  Autocompletion
133
133
  --------------
134
134
 
135
- It is necessary to copy the hammer_cli_complete script to the bash_completion.d directory.
136
-
137
- $ sudo cp hammer-cli/hammer_cli_complete /etc/bash_completion.d/
135
+ The completion offers suggestion of possible command-line subcommands and their
136
+ options as usual. It can also suggest values for options and params where file
137
+ or directory path is expected.
138
+
139
+ Bash completion is automatically installed by RPM. To use it for development
140
+ setup `cp ./config/hammer.completion /etc/bash_completion.d/hammer` and load it
141
+ to the current shell `source /etc/bash_completion.d/hammer`. Make sure
142
+ the `$PWD/bin` is in `PATH` or there is full path to `hammer-complete`
143
+ executable specified in `/etc/bash_completion.d/hammer`.
144
+
145
+ Bash completion for hammer needs pre-built cache that holds description of
146
+ all subcommands and its parameters. The cache is located by default in
147
+ `~/.cache/hammer_completion.yml`. The location can be changed in hammer's
148
+ config file. The cache can be built manually with
149
+ `hammer prebuild-bash-completion` or is built automatically when completion is
150
+ used and the cache is missing (this may cause slight delay). The cache expires
151
+ if your API cache was changed (it indicates that the features on the instance
152
+ may have changed which has impact on hammer CLI options and subcommands).
153
+
154
+ #### Available value types
155
+
156
+ Completion of values is dependent on CLI option and prameter settings, e.g.:
157
+
158
+ ```ruby
159
+ option '--value', 'VALUE', 'One of a, b, c', completion: { type: :enum, values: %w[a b c] }
160
+ ```
138
161
 
139
- Then after starting a new shell the completion should work.
162
+ Possible options for the `:completion` attribute are:
163
+ - `{ type: :flag }` option has no value, default for flags.
164
+ - `{ type: :value }` option has value of unknown type, no suggestions for the
165
+ value, default.
166
+ - `{ type: :list }` option has value of list type, no suggestions for the
167
+ value.
168
+ - `{ type: :key_value_list }` option has value of key=value list type, no
169
+ suggestions for the value.
170
+ - `{ type: :directory }` value is directory, suggestions follow directory
171
+ structure.
172
+ - `{ type: :file, filter: '\.txt$' }` value is file, suggestions follow
173
+ directory structure, optional `:filter` is regexp to filter the results.
174
+ - `{ type: :enum, values: ['first', 'second']}` option can have one of the
175
+ listed values, suggestions follow specified `values`.
176
+ - `{ type: :multienum, values: ['first', 'second']}` option can have one or
177
+ more of the listed values, suggestions follow specified `values`.
178
+ - `{ type: :schema, schema: 'a=int\,b=string' }` option should have value
179
+ according to specified schema, suggestion is the specified schema.
180
+
181
+ All those completion attributes are generated automatically, specify you own to
182
+ override.
data/doc/release_notes.md CHANGED
@@ -1,18 +1,26 @@
1
1
  Release notes
2
2
  =============
3
- ### 0.19.2 (2020-01-16)
4
- * Fixed userdata false display in image list ([PR #321](https://github.com/theforeman/hammer-cli/pull/321)), [#28134](http://projects.theforeman.org/issues/28134)
5
-
6
- ### 0.19.1 (2019-12-31)
3
+ ### 2.0.0 (2020-02-12)
4
+ * Bump version to 2.0.0
5
+ * Bump version to 2.0 ([PR #324](https://github.com/theforeman/hammer-cli/pull/324))
6
+ * Better promts for missing arguments, [#28793](http://projects.theforeman.org/issues/28793)
7
7
  * Allow column max width more than 80, [#28503](http://projects.theforeman.org/issues/28503)
8
+ * Remove computing sha, [#27728](http://projects.theforeman.org/issues/27728)
9
+ * Fixed userdata false display in image list, [#28134](http://projects.theforeman.org/issues/28134)
10
+ * Add new bash completion, [#27728](http://projects.theforeman.org/issues/27728)
11
+ * Allow adapters print page by page, [#17819](http://projects.theforeman.org/issues/17819)
12
+ * Add release documentation ([PR #317](https://github.com/theforeman/hammer-cli/pull/317)), [#28149](http://projects.theforeman.org/issues/28149)
13
+ * Fix pr links in release notes ([PR #318](https://github.com/theforeman/hammer-cli/pull/318)), [#28202](http://projects.theforeman.org/issues/28202)
8
14
  * Extract table generator into reusable component ([PR #314](https://github.com/theforeman/hammer-cli/pull/314)), [#27318](http://projects.theforeman.org/issues/27318)
15
+ * Better prompts for missing arguments ([PR #313](https://github.com/theforeman/hammer-cli/pull/313)), [#27595](http://projects.theforeman.org/issues/27595)
16
+ * Bump to 0.20-develop
9
17
 
10
18
  ### 0.19.0 (2019-10-26)
11
- * Allow schema building for custom options ([PR #316](https://github.com/Apipie/apipie-bindings/pull/316)), [#27899](http://projects.theforeman.org/issues/27899)
12
- * New lines in text attr dont break output ([PR #300](https://github.com/Apipie/apipie-bindings/pull/300)), [#25878](http://projects.theforeman.org/issues/25878)
13
- * Added error to wrong --output ([PR #315](https://github.com/Apipie/apipie-bindings/pull/315)), [#21590](http://projects.theforeman.org/issues/21590)
14
- * Pr review checklist ([PR #305](https://github.com/Apipie/apipie-bindings/pull/305)), [#26950](http://projects.theforeman.org/issues/26950)
15
- * List items in help with customization ([PR #309](https://github.com/Apipie/apipie-bindings/pull/309)), [#27237](http://projects.theforeman.org/issues/27237)
19
+ * Allow schema building for custom options ([PR #316](https://github.com/theforeman/hammer-cli/pull/316)), [#27899](http://projects.theforeman.org/issues/27899)
20
+ * New lines in text attr dont break output ([PR #300](https://github.com/theforeman/hammer-cli/pull/300)), [#25878](http://projects.theforeman.org/issues/25878)
21
+ * Added error to wrong --output ([PR #315](https://github.com/theforeman/hammer-cli/pull/315)), [#21590](http://projects.theforeman.org/issues/21590)
22
+ * Pr review checklist ([PR #305](https://github.com/theforeman/hammer-cli/pull/305)), [#26950](http://projects.theforeman.org/issues/26950)
23
+ * List items in help with customization ([PR #309](https://github.com/theforeman/hammer-cli/pull/309)), [#27237](http://projects.theforeman.org/issues/27237)
16
24
 
17
25
  ### 0.18.0 (2019-08-01)
18
26
  * Unsure minimal label length ([PR #310](https://github.com/theforeman/hammer-cli/pull/310)) ([#26960](http://projects.theforeman.org/issues/26960))
data/lib/hammer_cli.rb CHANGED
@@ -23,3 +23,4 @@ require 'hammer_cli/apipie'
23
23
  require 'hammer_cli/shell'
24
24
  require 'hammer_cli/defaults'
25
25
  require 'hammer_cli/full_help'
26
+ require 'hammer_cli/bash'
@@ -74,6 +74,7 @@ module HammerCLI
74
74
  begin
75
75
  begin
76
76
  exit_code = super
77
+ context.delete(:fields)
77
78
  raise "exit code must be integer" unless exit_code.is_a? Integer
78
79
  rescue => e
79
80
  exit_code = handle_exception(e)
@@ -194,6 +195,7 @@ module HammerCLI
194
195
  block ||= option.default_conversion_block
195
196
  define_accessors_for(option, &block)
196
197
  extend_options_help(option) if option.value_formatter.is_a?(HammerCLI::Options::Normalizers::ListNested)
198
+ completion_type_for(option)
197
199
  end
198
200
  end
199
201
 
@@ -242,8 +244,8 @@ module HammerCLI
242
244
  output.print_record(definition, record)
243
245
  end
244
246
 
245
- def print_collection(definition, collection)
246
- output.print_collection(definition, collection)
247
+ def print_collection(definition, collection, options = {})
248
+ output.print_collection(definition, collection, options)
247
249
  end
248
250
 
249
251
  def print_message(msg, msg_params = {}, options = {})
@@ -314,6 +316,7 @@ module HammerCLI
314
316
  declared_options << option
315
317
  block ||= option.default_conversion_block
316
318
  define_accessors_for(option, &block)
319
+ completion_type_for(option, opts)
317
320
  end
318
321
  extend_options_help(option) if option.value_formatter.is_a?(HammerCLI::Options::Normalizers::ListNested)
319
322
  option
@@ -352,6 +355,33 @@ module HammerCLI
352
355
  sources
353
356
  end
354
357
 
358
+ def self.completion_map
359
+ completion = {}
360
+ # collect options
361
+ recognised_options.each do |opt|
362
+ opt.switches.each do |switch|
363
+ completion[switch] = completion_types.fetch(switch, {})
364
+ end
365
+ end
366
+ # collect subcommands recursively
367
+ recognised_subcommands.each do |cmd|
368
+ completion[cmd.names.first] = cmd.subcommand_class.completion_map
369
+ end
370
+ # collect params
371
+ completion[:params] = completion_types[:params] unless completion_types[:params].empty?
372
+ completion
373
+ end
374
+
375
+ def self.completion_types
376
+ @completion_types ||= { :params => [] }
377
+ end
378
+
379
+ def self.completion_type_for(option, opts = {})
380
+ completion_type = opts.delete(:completion)
381
+ completion_type ||= option.completion_type(opts[:format])
382
+ [option.switches].flatten(1).each { |s| completion_types[s] = completion_type }
383
+ end
384
+
355
385
  private
356
386
 
357
387
  def self.inherited_output_definition
@@ -10,7 +10,11 @@ module HammerCLI::Apipie
10
10
  @api = ApipieBindings::API.new(params, HammerCLI::SSLOptions.new.get_options(params[:uri]))
11
11
  if options[:reload_cache]
12
12
  @api.clean_cache
13
- @logger.debug 'Apipie cache was cleared' unless @logger.nil?
13
+ HammerCLI.clear_cache
14
+ unless @logger.nil?
15
+ @logger.debug 'Apipie cache was cleared'
16
+ @logger.debug 'Completion cache was cleared'
17
+ end
14
18
  end
15
19
  end
16
20
 
@@ -65,8 +65,8 @@ module HammerCLI::Apipie
65
65
  method_options(options)
66
66
  end
67
67
 
68
- def print_data(data)
69
- print_collection(output_definition, data) unless output_definition.empty?
68
+ def print_data(data, options = {})
69
+ print_collection(output_definition, data, options) unless output_definition.empty?
70
70
  print_success_message(data) unless success_message.nil?
71
71
  end
72
72
 
@@ -86,6 +86,7 @@ module HammerCLI::Apipie
86
86
  declared_options << option
87
87
  block ||= option.default_conversion_block
88
88
  define_accessors_for(option, &block)
89
+ completion_type_for(option, opts)
89
90
  end
90
91
  extend_options_help(option) if option.value_formatter.is_a?(HammerCLI::Options::Normalizers::ListNested)
91
92
  option
@@ -0,0 +1,2 @@
1
+ require 'hammer_cli/bash/prebuild_command'
2
+ require 'hammer_cli/bash/completion'
@@ -0,0 +1,159 @@
1
+ require 'json'
2
+
3
+ module HammerCLI
4
+ module Bash
5
+ class Completion
6
+ def initialize(dict)
7
+ @dict = dict
8
+ end
9
+
10
+ def complete(line)
11
+ @complete_line = line.end_with?(' ')
12
+ full_path = line.split(' ')
13
+ complete_path = @complete_line ? full_path : full_path[0..-2]
14
+ dict, path = traverse_tree(@dict, complete_path)
15
+
16
+ return [] unless path.empty? # lost during traversing
17
+
18
+ partial = @complete_line ? '' : full_path.last
19
+ finish_word(dict, partial)
20
+ end
21
+
22
+ def self.load_description(path)
23
+ JSON.load(File.open(path))
24
+ rescue Errno::ENOENT
25
+ {}
26
+ end
27
+
28
+ private
29
+
30
+ def finish_word(dict, incomplete)
31
+ finish_option_value(dict, incomplete) ||
32
+ (finish_option_or_subcommand(dict, incomplete) + finish_param(dict, incomplete))
33
+ end
34
+
35
+ def finish_option_or_subcommand(dict, incomplete)
36
+ dict.keys.select { |k| k.is_a?(String) && k =~ /^#{incomplete}/ }.map { |k| k + ' ' }
37
+ end
38
+
39
+ def complete_value(value_description, partial, is_param)
40
+ case value_description['type']
41
+ when 'value'
42
+ if !partial.empty?
43
+ []
44
+ elsif is_param
45
+ ['--->', 'Add parameter']
46
+ else
47
+ ['--->', 'Add option <value>']
48
+ end
49
+ when 'directory'
50
+ directories(partial)
51
+ when 'file'
52
+ files(partial, value_description)
53
+ when 'enum'
54
+ enum(partial, value_description['values'])
55
+ when 'multienum'
56
+ multienum(partial, value_description['values'])
57
+ when 'schema'
58
+ schema(value_description['schema'])
59
+ when 'list'
60
+ ['--->', 'Add comma-separated list of values']
61
+ when 'key_value_list'
62
+ ['--->', 'Add comma-separated list of key=value']
63
+ end
64
+ end
65
+
66
+ def finish_param(dict, incomplete)
67
+ if dict['params'] && !dict['params'].empty?
68
+ complete_value(dict['params'].first, incomplete, true)
69
+ else
70
+ []
71
+ end
72
+ end
73
+
74
+ def finish_option_value(dict, incomplete)
75
+ complete_value(dict, incomplete, false) if dict.key?('type')
76
+ end
77
+
78
+ def traverse_tree(dict, path)
79
+ return [dict, []] if path.nil? || path.empty?
80
+ result = if dict.key?(path.first)
81
+ if path.first.start_with?('-')
82
+ parse_option(dict, path)
83
+ else
84
+ parse_subcommand(dict, path)
85
+ end
86
+ elsif dict['params']
87
+ # traverse params one by one
88
+ parse_params(dict, path)
89
+ else
90
+ # not found
91
+ [{}, path]
92
+ end
93
+ result
94
+ end
95
+
96
+ def parse_params(dict, path)
97
+ traverse_tree({ 'params' => dict['params'][1..-1] }, path[1..-1])
98
+ end
99
+
100
+ def parse_subcommand(dict, path)
101
+ traverse_tree(dict[path.first], path[1..-1])
102
+ end
103
+
104
+ def parse_option(dict, path)
105
+ if dict[path.first]['type'] == 'flag' # flag
106
+ traverse_tree(dict, path[1..-1])
107
+ elsif path.length >= 2 # option with value
108
+ traverse_tree(dict, path[2..-1])
109
+ else # option with value missing
110
+ [dict[path.first], path[1..-1]]
111
+ end
112
+ end
113
+
114
+ def directories(partial = '')
115
+ dirs = []
116
+ dirs += Dir.glob("#{partial}*").select { |f| File.directory?(f) }
117
+ dirs = dirs.map { |d| d + '/' } if dirs.length == 1
118
+ dirs
119
+ end
120
+
121
+ def files(partial = '', opts = {})
122
+ filter = opts.fetch('filter', '.*')
123
+ file_names = []
124
+ file_names += Dir.glob("#{partial}*").select do |f|
125
+ File.directory?(f) || f =~ /#{filter}/
126
+ end
127
+ file_names.map { |f| File.directory?(f) ? f + '/' : f + ' ' }
128
+ end
129
+
130
+ def enum(partial = '', values = [])
131
+ values.select { |v| v.start_with?(partial) }.map { |v| v + ' ' }
132
+ end
133
+
134
+ def multienum(partial = '', values = [])
135
+ return values if partial.empty?
136
+
137
+ parts = partial.split(',')
138
+ resolved = []
139
+ to_complete = parts.each_with_object([]) do |part, res|
140
+ next resolved << part if values.include?(part)
141
+
142
+ res << part
143
+ end
144
+
145
+ hints = to_complete.map do |p|
146
+ values.select { |v| v.start_with?(p) }
147
+ end.flatten(1).uniq
148
+ return values - parts if hints.empty?
149
+ return [(resolved + hints).join(',')] if hints.size == 1
150
+
151
+ hints
152
+ end
153
+
154
+ def schema(template = '')
155
+ ['--->', "Add value by following schema: #{template}"]
156
+ end
157
+ end
158
+ end
159
+ end