hammer_cli 0.0.18 → 0.1.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -314
  3. data/bin/hammer +45 -6
  4. data/config/cli.modules.d/module_config_template.yml +4 -0
  5. data/config/cli_config.template.yml +9 -11
  6. data/doc/developer_docs.md +1 -0
  7. data/doc/i18n.md +85 -0
  8. data/doc/installation.md +321 -0
  9. data/lib/hammer_cli.rb +3 -0
  10. data/lib/hammer_cli/abstract.rb +15 -24
  11. data/lib/hammer_cli/apipie/command.rb +13 -7
  12. data/lib/hammer_cli/apipie/options.rb +14 -16
  13. data/lib/hammer_cli/apipie/read_command.rb +6 -1
  14. data/lib/hammer_cli/apipie/resource.rb +48 -58
  15. data/lib/hammer_cli/apipie/write_command.rb +5 -1
  16. data/lib/hammer_cli/completer.rb +77 -21
  17. data/lib/hammer_cli/connection.rb +44 -0
  18. data/lib/hammer_cli/exception_handler.rb +15 -4
  19. data/lib/hammer_cli/exceptions.rb +6 -0
  20. data/lib/hammer_cli/i18n.rb +95 -0
  21. data/lib/hammer_cli/logger.rb +3 -3
  22. data/lib/hammer_cli/main.rb +12 -11
  23. data/lib/hammer_cli/modules.rb +19 -6
  24. data/lib/hammer_cli/options/normalizers.rb +42 -7
  25. data/lib/hammer_cli/options/option_definition.rb +2 -2
  26. data/lib/hammer_cli/output.rb +1 -0
  27. data/lib/hammer_cli/output/adapter/abstract.rb +20 -0
  28. data/lib/hammer_cli/output/adapter/base.rb +49 -78
  29. data/lib/hammer_cli/output/adapter/csv.rb +5 -5
  30. data/lib/hammer_cli/output/adapter/table.rb +41 -10
  31. data/lib/hammer_cli/output/dsl.rb +1 -1
  32. data/lib/hammer_cli/output/field_filter.rb +21 -0
  33. data/lib/hammer_cli/output/fields.rb +44 -78
  34. data/lib/hammer_cli/output/formatters.rb +38 -0
  35. data/lib/hammer_cli/settings.rb +28 -6
  36. data/lib/hammer_cli/shell.rb +58 -57
  37. data/lib/hammer_cli/utils.rb +14 -0
  38. data/lib/hammer_cli/validator.rb +5 -5
  39. data/lib/hammer_cli/version.rb +1 -1
  40. data/locale/Makefile +64 -0
  41. data/locale/hammer-cli.pot +203 -0
  42. data/locale/zanata.xml +29 -0
  43. data/test/unit/apipie/command_test.rb +42 -25
  44. data/test/unit/apipie/read_command_test.rb +10 -7
  45. data/test/unit/apipie/write_command_test.rb +9 -8
  46. data/test/unit/completer_test.rb +206 -21
  47. data/test/unit/connection_test.rb +68 -0
  48. data/test/unit/fixtures/apipie/architectures.json +153 -0
  49. data/test/unit/fixtures/apipie/documented.json +79 -0
  50. data/test/unit/fixtures/json_input/invalid.json +12 -0
  51. data/test/unit/fixtures/json_input/valid.json +12 -0
  52. data/test/unit/history_test.rb +71 -0
  53. data/test/unit/main_test.rb +9 -0
  54. data/test/unit/modules_test.rb +22 -6
  55. data/test/unit/options/field_filter_test.rb +27 -0
  56. data/test/unit/options/normalizers_test.rb +53 -0
  57. data/test/unit/output/adapter/base_test.rb +162 -10
  58. data/test/unit/output/adapter/csv_test.rb +16 -3
  59. data/test/unit/output/adapter/table_test.rb +97 -13
  60. data/test/unit/output/dsl_test.rb +74 -6
  61. data/test/unit/output/fields_test.rb +93 -62
  62. data/test/unit/output/formatters_test.rb +47 -0
  63. data/test/unit/settings_test.rb +35 -4
  64. data/test/unit/utils_test.rb +45 -0
  65. metadata +85 -4
  66. data/test/unit/apipie/fake_api.rb +0 -101
@@ -28,7 +28,7 @@ module HammerCLI::Apipie
28
28
  end
29
29
 
30
30
  def self.desc(desc=nil)
31
- super(desc) || resource.docs_for(action)["apis"][0]["short_description"]
31
+ super(desc) || resource.action(action).apidoc[:apis][0][:short_description] || " "
32
32
  rescue
33
33
  " "
34
34
  end
@@ -65,9 +65,9 @@ module HammerCLI::Apipie
65
65
  private
66
66
 
67
67
  def self.setup_identifier_options
68
- identifier_option(:id, "resource id", declared_identifiers[:id]) if identifier? :id
69
- identifier_option(:name, "resource name", declared_identifiers[:name]) if identifier? :name
70
- identifier_option(:label, "resource label", declared_identifiers[:label]) if identifier? :label
68
+ identifier_option(:id, _("resource id"), declared_identifiers[:id]) if identifier? :id
69
+ identifier_option(:name, _("resource name"), declared_identifiers[:name]) if identifier? :name
70
+ identifier_option(:label, _("resource label"), declared_identifiers[:label]) if identifier? :label
71
71
  end
72
72
 
73
73
  def self.identifier_option(name, desc, attr_name)
@@ -83,10 +83,16 @@ module HammerCLI::Apipie
83
83
  end
84
84
 
85
85
  def name_to_id(name, option_name, resource)
86
- results = resource.call(:index, :search => "#{option_name} = #{name}")[0]
86
+ results = resource.call(:index, :search => "#{option_name} = #{name}")
87
87
  results = HammerCLIForeman.collection_to_common_format(results)
88
- raise "#{resource.name} with #{option_name} '#{name}' not found" if results.empty?
89
- raise "#{resource.name} with #{option_name} '#{name}' found more than once" if results.count > 1
88
+
89
+ msg_opts = {
90
+ :resource => resource.name,
91
+ :option => option_name,
92
+ :value => name
93
+ }
94
+ raise _("%{resource} with %{option} '%{value}' not found") % msg_opts if results.empty?
95
+ raise _("%{resource} with %{option} '%{value}' found more than once") % msg_opts if results.count > 1
90
96
  results[0]['id']
91
97
  end
92
98
 
@@ -7,20 +7,20 @@ module HammerCLI::Apipie
7
7
  end
8
8
 
9
9
  def all_method_options
10
- method_options_for_params(resource.docs_for(action)["params"], true)
10
+ method_options_for_params(resource.action(action).params, true)
11
11
  end
12
12
 
13
13
  def method_options
14
- method_options_for_params(resource.docs_for(action)["params"], false)
14
+ method_options_for_params(resource.action(action).params, false)
15
15
  end
16
16
 
17
17
  def method_options_for_params(params, include_nil=true)
18
18
  opts = {}
19
19
  params.each do |p|
20
- if p["expected_type"] == "hash"
21
- opts[p["name"]] = method_options_for_params(p["params"], include_nil)
20
+ if p.expected_type == :hash
21
+ opts[p.name] = method_options_for_params(p.params, include_nil)
22
22
  else
23
- opts[p["name"]] = get_option_value(p["name"])
23
+ opts[p.name] = get_option_value(p.name)
24
24
  end
25
25
  end
26
26
  opts.reject! {|key, value| value.nil? } unless include_nil
@@ -44,7 +44,7 @@ module HammerCLI::Apipie
44
44
  filter = Array(filter)
45
45
  filter += declared_identifiers.keys
46
46
 
47
- options_for_params(resource.docs_for(action)["params"], filter)
47
+ options_for_params(resource.action(action).params, filter)
48
48
  end
49
49
  end
50
50
 
@@ -52,9 +52,9 @@ module HammerCLI::Apipie
52
52
 
53
53
  def options_for_params(params, filter)
54
54
  params.each do |p|
55
- next if filter.include? p["name"].to_s or filter.include? p["name"].to_sym
56
- if p["expected_type"] == "hash"
57
- options_for_params(p["params"], filter)
55
+ next if filter.include?(p.name) || filter.include?(p.name.to_sym)
56
+ if p.expected_type == :hash
57
+ options_for_params(p.params, filter)
58
58
  else
59
59
  create_option p
60
60
  end
@@ -71,25 +71,23 @@ module HammerCLI::Apipie
71
71
  end
72
72
 
73
73
  def option_switches(param)
74
- '--' + param["name"].gsub('_', '-')
74
+ '--' + param.name.gsub('_', '-')
75
75
  end
76
76
 
77
77
  def option_type(param)
78
- param["name"].upcase.gsub('-', '_')
78
+ param.name.upcase.gsub('-', '_')
79
79
  end
80
80
 
81
81
  def option_desc(param)
82
- desc = param["description"].gsub(/<\/?[^>]+?>/, "")
83
- return " " if desc.empty?
84
- return desc
82
+ param.description || " "
85
83
  end
86
84
 
87
85
  def option_opts(param)
88
86
  opts = {}
89
- opts[:required] = true if param["required"]
87
+ opts[:required] = true if param.required?
90
88
  # FIXME: There is a bug in apipie, it does not produce correct expected type for Arrays
91
89
  # When it's fixed, we should test param["expected_type"] == "array"
92
- opts[:format] = HammerCLI::Options::Normalizers::List.new if param["validator"].include? "Array"
90
+ opts[:format] = HammerCLI::Options::Normalizers::List.new if param.validator.include? "Array"
93
91
  return opts
94
92
  end
95
93
 
@@ -14,7 +14,12 @@ module HammerCLI::Apipie
14
14
  protected
15
15
  def retrieve_data
16
16
  raise "resource or action not defined" unless self.class.resource_defined?
17
- resource.call(action, request_params, request_headers)[0]
17
+ logger.debug request_params.ai
18
+ if resource && resource.has_action?(action)
19
+ resource.call(action, request_params, request_headers)
20
+ else
21
+ raise HammerCLI::OperationNotSupportedError, "The server does not support such operation."
22
+ end
18
23
  end
19
24
 
20
25
  def print_data(records)
@@ -1,64 +1,36 @@
1
+ require 'apipie_bindings'
1
2
  module HammerCLI::Apipie
2
3
 
3
- class ResourceDefinition
4
+ class AbstractCredentials
4
5
 
5
- attr_reader :resource_class
6
-
7
- def initialize(resource_class)
8
- @resource_class = resource_class
9
- end
10
-
11
- def name
12
- resource_class.name.split("::")[-1].downcase
6
+ def to_params
7
+ {}
13
8
  end
14
9
 
15
- def plural_name
16
- irregular_names = {
17
- "statistics" => "statistics",
18
- "home" => "home",
19
- "host_class" => "host_classes",
20
- "medium" => "media",
21
- "puppetclass" => "puppetclasses",
22
- "dashboard" => "dashboard",
23
- "smart_proxy" => "smart_proxies",
24
- "settings" => "settings",
25
- "hostgroup_class" => "hostgroup_classes"
26
- }
27
- irregular_names[name] || "%ss" % name
28
- end
10
+ private
29
11
 
30
- def docs_for(method_name)
31
- resource_class.doc["methods"].each do |method|
32
- return method if method["name"] == method_name.to_s
12
+ def ask_user(prompt, silent=false)
13
+ if silent
14
+ ask(prompt) {|q| q.echo = false}
15
+ else
16
+ ask(prompt)
33
17
  end
34
- raise "No method documentation found for #{resource_class}##{method_name}"
35
18
  end
36
19
 
37
20
  end
38
21
 
39
22
 
40
- class ResourceInstance < ResourceDefinition
23
+ class ApipieConnector < HammerCLI::AbstractConnector
41
24
 
42
- def initialize(resource_class, config)
43
- super(resource_class)
44
- @instance = resource_class.new(config)
45
- end
25
+ attr_reader :api
46
26
 
47
- def self.from_definition(definition, config)
48
- self.new(definition.resource_class, config)
49
- end
27
+ def initialize(params)
28
+ credentials = params.delete(:credentials)
29
+ params.merge!(credentials.to_params) if credentials
50
30
 
51
- def call(method_name, params=nil, headers=nil)
52
- Logging.logger[resource_class.name].debug "Calling '#{method_name}' with params #{params.ai}" if HammerCLI::Settings.get(:log_api_calls)
53
- result = instance.send(method_name, params, headers)
54
- Logging.logger[resource_class.name].debug "Method '#{method_name}' responded with #{result[0].ai}" if HammerCLI::Settings.get(:log_api_calls)
55
- result
31
+ @api = ApipieBindings::API.new(params)
56
32
  end
57
33
 
58
- private
59
-
60
- attr_reader :instance
61
-
62
34
  end
63
35
 
64
36
 
@@ -69,13 +41,7 @@ module HammerCLI::Apipie
69
41
  end
70
42
 
71
43
  def resource
72
- # if the resource definition is not available in this command's class
73
- # or its superclass try to look it up in parent command's class
74
- if self.class.resource
75
- return ResourceInstance.from_definition(self.class.resource, resource_config)
76
- else
77
- return ResourceInstance.from_definition(self.parent_command.class.resource, resource_config)
78
- end
44
+ self.class.resource || self.parent_command.class.resource
79
45
  end
80
46
 
81
47
  def action
@@ -83,15 +49,29 @@ module HammerCLI::Apipie
83
49
  end
84
50
 
85
51
  def resource_config
86
- config = {}
87
- config[:base_url] = HammerCLI::Settings.get(:foreman, :host)
88
- config[:username] = username
89
- config[:password] = password
90
- config
52
+ self.class.resource_config
53
+ end
54
+
55
+ def connection_options
56
+ self.class.connection_options
91
57
  end
92
58
 
93
59
  module ClassMethods
94
60
 
61
+ def resource_config
62
+ {}
63
+ end
64
+
65
+ def connection_options
66
+ {
67
+ :connector => HammerCLI::Apipie::ApipieConnector
68
+ }
69
+ end
70
+
71
+ def connection_name(resource_class)
72
+ :apipie
73
+ end
74
+
95
75
  def class_resource
96
76
  return @api_resource if @api_resource
97
77
  return superclass.class_resource if superclass.respond_to? :class_resource
@@ -106,8 +86,18 @@ module HammerCLI::Apipie
106
86
  end
107
87
  end
108
88
 
109
- def resource(resource_class=nil, action=nil)
110
- @api_resource = ResourceDefinition.new(resource_class) unless resource_class.nil?
89
+ def resource(resource=nil, action=nil)
90
+ unless resource.nil?
91
+ api = HammerCLI::Connection.create(
92
+ connection_name(resource),
93
+ resource_config,
94
+ connection_options).api
95
+ if api.has_resource?(resource)
96
+ @api_resource = api.resource(resource)
97
+ else
98
+ logger.warn "Resource '#{resource}' does not exist in the API"
99
+ end
100
+ end
111
101
  @api_action = action unless action.nil?
112
102
 
113
103
  # if the resource definition is not available in this class
@@ -27,7 +27,11 @@ module HammerCLI::Apipie
27
27
  end
28
28
 
29
29
  def send_request
30
- resource.call(action, request_params, request_headers)[0]
30
+ if resource && resource.has_action?(action)
31
+ resource.call(action, request_params, request_headers)
32
+ else
33
+ raise HammerCLI::OperationNotSupportedError, "The server does not support such operation."
34
+ end
31
35
  end
32
36
 
33
37
  def request_headers
@@ -1,30 +1,77 @@
1
1
  require 'enumerator'
2
2
 
3
+
3
4
  module HammerCLI
4
5
 
6
+ # Single "word" on a command line to complete.
7
+ # It contains trailing spaces to recognize whether the word is complete or not.
8
+ # --param[ ]* or -flag[ ]* or ['"]?word['"]?[ ]*
9
+ class CompleterWord < String
10
+
11
+ def initialize(str)
12
+ @original = str
13
+ if quoted?
14
+ str = str.gsub(/^['"]/, '').gsub(/['"]\s*$/, '')
15
+ else
16
+ str = str.strip
17
+ end
18
+ super(str)
19
+ end
20
+
21
+ def quoted?
22
+ quote != ""
23
+ end
24
+
25
+ def quote
26
+ @original.gsub(/^(['"]?)(.*)$/, '\1')
27
+ end
28
+
29
+ def complete?
30
+ if quoted?
31
+ @original.strip.gsub(/^['"].*['"][\s]*$/, '') == ""
32
+ else
33
+ @original[-1,1] == " "
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+
40
+ # Array of command line words for completion.
41
+ # Splits string line to "words" with trailing spaces.
42
+ # --param[=]?[ ]* or -flag[ ]* or ['"]word['"]?[ ]*
5
43
  class CompleterLine < Array
6
44
 
7
45
  def initialize(line)
8
46
  @line = line
9
- super(line.split)
47
+ super(split_line)
48
+ end
49
+
50
+ def complete?
51
+ self.empty? || self.last.complete?
10
52
  end
11
53
 
12
- def finished?
13
- (@line[-1,1] == " ") || @line.empty?
54
+ protected
55
+
56
+ def split_line
57
+ @line.scan(/-[\w\-]+=?[\s]*|["][^"]*["]?[\s]*|['][^']*[']?[\s]*|[^\s]+[\s]*/).collect do |word|
58
+ CompleterWord.new(word.gsub(/=$/, ' '))
59
+ end
14
60
  end
15
61
 
16
62
  end
17
63
 
64
+
18
65
  class Completer
19
66
 
20
67
  def initialize(cmd_class)
21
68
  @command = cmd_class
22
69
  end
23
70
 
24
-
25
71
  def complete(line)
26
72
  line = CompleterLine.new(line)
27
73
 
74
+ # get the last command on the line
28
75
  cmd, remaining = find_last_cmd(line)
29
76
 
30
77
  opt, value = option_to_complete(cmd, remaining)
@@ -33,7 +80,7 @@ module HammerCLI
33
80
  else
34
81
  param, value = param_to_complete(cmd, remaining)
35
82
  if param
36
- if remaining.finished?
83
+ if remaining.complete?
37
84
  return complete_attribute(param, value) + complete_command(cmd, remaining)
38
85
  else
39
86
  return complete_attribute(param, value)
@@ -50,12 +97,25 @@ module HammerCLI
50
97
 
51
98
  def complete_attribute(attribute, value)
52
99
  if attribute.respond_to?(:complete)
53
- filter(attribute.complete(value), value)
100
+ if value != nil and value.quoted?
101
+ filter(attribute.complete(value), value).map do |completion|
102
+ quote_value(completion, value.quote)
103
+ end
104
+ else
105
+ filter(attribute.complete(value), value)
106
+ end
54
107
  else
55
108
  []
56
109
  end
57
110
  end
58
111
 
112
+ def quote_value(val, quotes)
113
+ if val[-1,1] == ' '
114
+ quotes + val.strip + quotes + ' '
115
+ else
116
+ quotes + val
117
+ end
118
+ end
59
119
 
60
120
  def param_to_complete(cmd, line)
61
121
  params = cmd.parameters.select do |p|
@@ -73,7 +133,7 @@ module HammerCLI
73
133
 
74
134
  param = nil
75
135
 
76
- if line.finished?
136
+ if line.complete?
77
137
  # "--option " or "--option xx " or "xx "
78
138
  value = nil
79
139
  param_index = param_candidates.size
@@ -94,33 +154,35 @@ module HammerCLI
94
154
  return [param, value]
95
155
  end
96
156
 
97
-
98
157
  def option_to_complete(cmd, line)
99
158
  return [nil, nil] if line.empty?
100
159
 
101
- if line.finished?
160
+ if line.complete?
102
161
  # last word must be option and can't be flag -> we complete the value
103
- # "--option " nebo "--option xx "
104
- opt = cmd.find_option(line[-1])
162
+ # "--option " or "--option xx "
163
+ opt = find_option(cmd, line[-1])
105
164
  return [opt, nil] if opt and not opt.flag?
106
165
  else
107
166
  # we complete the value in the second case
108
167
  # "--opt" or "--option xx" or "xx yy"
109
- opt = cmd.find_option(line[-2])
168
+ opt = find_option(cmd, line[-2])
110
169
  return [opt, line[-1]] if opt and not opt.flag?
111
170
  end
112
171
  return [nil, nil]
113
172
  end
114
173
 
174
+ def find_option(cmd, switch)
175
+ cmd.find_option(switch) unless switch.nil?
176
+ end
115
177
 
116
178
  def find_last_cmd(line)
117
179
  cmd = @command
118
180
  subcommands = sub_command_map(cmd)
119
181
 
120
- # if the last word is not finished we have to select it's parent
182
+ # if the last word is not complete we have to select it's parent
121
183
  # -> shorten the line
122
184
  words = line.dup
123
- words.pop unless line.finished?
185
+ words.pop unless line.complete?
124
186
 
125
187
  cmd_idx = 0
126
188
  words.each_with_index do |word, idx|
@@ -139,21 +201,19 @@ module HammerCLI
139
201
  return [cmd, remaining]
140
202
  end
141
203
 
142
-
143
204
  def complete_command(command, remaining)
144
205
  completions = []
145
206
  completions += sub_command_names(command)
146
207
  completions += command_options(command)
147
208
  completions = Completer::finalize_completions(completions)
148
209
 
149
- if remaining.finished?
210
+ if remaining.complete?
150
211
  return completions
151
212
  else
152
213
  return filter(completions, remaining.last)
153
214
  end
154
215
  end
155
216
 
156
-
157
217
  def filter(completions, last_word)
158
218
  if last_word.to_s != ""
159
219
  completions.select{|name| name.start_with? last_word }
@@ -162,12 +222,10 @@ module HammerCLI
162
222
  end
163
223
  end
164
224
 
165
-
166
225
  def self.finalize_completions(completions)
167
226
  completions.collect{|name| name+' ' }
168
227
  end
169
228
 
170
-
171
229
  def sub_command_map(cmd_class)
172
230
  cmd_class.recognised_subcommands.inject({}) do |cmd_map, cmd|
173
231
  cmd.names.each do |name|
@@ -177,12 +235,10 @@ module HammerCLI
177
235
  end
178
236
  end
179
237
 
180
-
181
238
  def sub_command_names(cmd_class)
182
239
  sub_command_map(cmd_class).keys.flatten
183
240
  end
184
241
 
185
-
186
242
  def command_options(cmd_class)
187
243
  cmd_class.recognised_options.inject([]) do |opt_switches, opt|
188
244
  opt_switches += opt.switches