hammer_cli 0.0.18 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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