hammer_cli 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +13 -5
  3. data/config/cli_config.template.yml +12 -3
  4. data/doc/creating_apipie_commands.md +53 -56
  5. data/doc/creating_commands.md +25 -19
  6. data/doc/developer_docs.md +3 -2
  7. data/doc/development_tips.md +3 -3
  8. data/doc/i18n.md +3 -3
  9. data/doc/installation.md +24 -207
  10. data/doc/installation_deb.md +48 -0
  11. data/doc/installation_gem.md +30 -0
  12. data/doc/installation_rpm.md +53 -0
  13. data/doc/installation_source.md +31 -0
  14. data/doc/option_builders.md +77 -0
  15. data/doc/option_normalizers.md +5 -5
  16. data/doc/writing_a_plugin.md +9 -9
  17. data/lib/hammer_cli.rb +1 -0
  18. data/lib/hammer_cli/abstract.rb +21 -8
  19. data/lib/hammer_cli/apipie.rb +1 -2
  20. data/lib/hammer_cli/apipie/command.rb +37 -64
  21. data/lib/hammer_cli/apipie/option_builder.rb +69 -0
  22. data/lib/hammer_cli/apipie/options.rb +1 -61
  23. data/lib/hammer_cli/clamp.rb +15 -0
  24. data/lib/hammer_cli/i18n.rb +14 -2
  25. data/lib/hammer_cli/logger.rb +1 -1
  26. data/lib/hammer_cli/option_builder.rb +43 -0
  27. data/lib/hammer_cli/options/option_definition.rb +6 -0
  28. data/lib/hammer_cli/output/adapter/abstract.rb +2 -2
  29. data/lib/hammer_cli/output/adapter/base.rb +6 -3
  30. data/lib/hammer_cli/output/adapter/table.rb +19 -2
  31. data/lib/hammer_cli/output/definition.rb +4 -0
  32. data/lib/hammer_cli/output/fields.rb +9 -0
  33. data/lib/hammer_cli/output/formatters.rb +20 -8
  34. data/lib/hammer_cli/utils.rb +1 -1
  35. data/lib/hammer_cli/version.rb +1 -1
  36. data/locale/hammer-cli.pot +103 -79
  37. data/test/unit/abstract_test.rb +62 -0
  38. data/test/unit/apipie/command_test.rb +12 -197
  39. data/test/unit/apipie/option_builder_test.rb +110 -0
  40. data/test/unit/i18n_test.rb +50 -0
  41. data/test/unit/option_builder_test.rb +33 -0
  42. data/test/unit/output/adapter/abstract_test.rb +26 -3
  43. data/test/unit/output/adapter/base_test.rb +20 -3
  44. data/test/unit/output/adapter/csv_test.rb +2 -2
  45. data/test/unit/output/adapter/table_test.rb +24 -1
  46. data/test/unit/output/definition_test.rb +13 -0
  47. data/test/unit/output/formatters_test.rb +17 -1
  48. data/test/unit/utils_test.rb +1 -1
  49. metadata +101 -88
  50. data/lib/hammer_cli/apipie/read_command.rb +0 -41
  51. data/lib/hammer_cli/apipie/write_command.rb +0 -49
  52. data/test/unit/apipie/read_command_test.rb +0 -37
  53. data/test/unit/apipie/write_command_test.rb +0 -41
@@ -0,0 +1,31 @@
1
+ ### Installation from SOURCE
2
+
3
+ If you can install hammer from git checkouts, you will just need `rake` installed on your system.
4
+
5
+ #### Step 1: clone the sources
6
+ Clone and install CLI core
7
+
8
+ ```bash
9
+ $ git clone https://github.com/theforeman/hammer-cli.git
10
+ $ cd hammer-cli
11
+ $ rake install
12
+ $ cd ..
13
+ ```
14
+
15
+ clone plugin with foreman commands
16
+
17
+ ```bash
18
+ $ git clone https://github.com/theforeman/hammer-cli-foreman.git
19
+ $ cd hammer-cli-foreman
20
+ $ rake install
21
+ $ cd ..
22
+ ```
23
+
24
+ and optionally other plugins.
25
+
26
+
27
+ #### Step 2: enable and configure the plugins
28
+ You'll have to copy configuration files to proper locations manually.
29
+ Please check our [configuration instructions](installation.md#configuration)
30
+ and see how to proceed.
31
+
@@ -0,0 +1,77 @@
1
+ Option builders
2
+ ---------------
3
+
4
+ Option builders offer a mechanism for dynamic option creation.
5
+ This is useful in situations when there's a common pattern or options
6
+ can be created automatically based on some data.
7
+
8
+ The [builder's interface](https://github.com/theforeman/hammer-cli/blob/master/lib/hammer_cli/option_builder.rb)
9
+ is very simple. It has to define the only method `build(builder_params={})` that returns
10
+ array of `HammerCLI::Options::OptionDefinition` instances.
11
+
12
+ Example of a simple option builder follows:
13
+ ```ruby
14
+ class DimensionsOptionBuilder < AbstractOptionBuilder
15
+
16
+ def initialize(option_names)
17
+ @option_names = option_names
18
+ end
19
+
20
+ def build(builder_params={})
21
+ opts = []
22
+ @option_names.each do |name|
23
+ opts << option("--#{name}-height", "#{name.upcase}_HEIGHT", "Height of the #{name}")
24
+ opts << option("--#{name}-width", "#{name.upcase}_WIDTH", "Width of the #{name}")
25
+ end
26
+ opts
27
+ end
28
+
29
+ end
30
+ ```
31
+
32
+ Each command class has an option builder defined in method `option_builder`.
33
+ It is executed by calling class method `build_options`.
34
+
35
+ ```ruby
36
+ class HouseCommand < HammerCLI::AbstractCommand
37
+
38
+ # define your own option builder
39
+ def self.option_builder
40
+ DimensionsOptionBuilder.new(["door", "window"])
41
+ end
42
+
43
+ # create the options at class level
44
+ build_options
45
+ end
46
+ ```
47
+
48
+ ```
49
+ hammer house -h
50
+ Usage:
51
+ hammer house [OPTIONS]
52
+
53
+ --door-height DOOR_HEIGHT Height of the door
54
+ --door-width DOOR_WIDTH Width of the door
55
+ --window-height WINDOW_HEIGHT Height of the window
56
+ --window-width WINDOW_WIDTH Width of the window
57
+ -h, --help print help
58
+ ```
59
+
60
+ The default option builder is `OptionBuilderContainer` that
61
+ is useful for chaining multiple builders. Command's class
62
+ method `custom_option_builders` is there exactly for this reason. It's output
63
+ is passed to the container and can be used for defining custom builders.
64
+
65
+ ```ruby
66
+ def self.custom_option_builders
67
+ [
68
+ DimensionsOptionBuilder.new(["door", "window"]),
69
+ IdentifierOptionBuilder.new,
70
+ SomeOtherCoolBuilder.new
71
+ ]
72
+ end
73
+ ```
74
+
75
+ If an option with the same `--flag` is already defined (either statically or from another builder)
76
+ any other option with the same flag is ignored.
77
+
@@ -15,7 +15,7 @@ option "--enabled", "ENABLED", "Should the host be enabled?",
15
15
  ```
16
16
 
17
17
  #### Description of valid values
18
- There is method `description` that should return a help string. It's value is used in output of `-h`,
18
+ There is a method `description` that should return a help string. It's value is used in the output of `-h`,
19
19
  which can then look for example like this:
20
20
 
21
21
  ```
@@ -23,15 +23,15 @@ which can then look for example like this:
23
23
  One of true/false, yes/no, 1/0.
24
24
  ```
25
25
 
26
- Abstract normalizer returns empty string by default.
26
+ Abstract normalizer returns an empty string by default.
27
27
 
28
28
 
29
29
  #### Check and format passed values
30
30
 
31
31
  Normalizer's method `format` is used for such checks. The method behaves as a filter taking
32
- string value as it's input and returning value of any type.
32
+ a string value as it's input and returning a value of any type.
33
33
 
34
- If the value is not valid `ArgumentError` with an appropriate message should be risen.
34
+ If the value is not valid, `ArgumentError` with an appropriate message should be risen.
35
35
  Implementation in `Bool` normalizer is a good example of such functionality:
36
36
 
37
37
  ```ruby
@@ -50,7 +50,7 @@ end
50
50
 
51
51
  #### Value completion
52
52
 
53
- Normalizers can also provide completion of option values via method `complete`. It takes one argument - current option value at the time the completion was requested. In the simplest cases the method returns array of all possible values. More complex completions can use the current value argument for building the return values.
53
+ Normalizers can also provide completion of option values via method `complete`. It takes one argument - the current option value at the time the completion was requested. In the simplest cases the method returns array of all possible values. More complex completions can use the current value argument for building the return values.
54
54
 
55
55
  We distinguish two types of offered completion strings:
56
56
 
@@ -4,7 +4,7 @@ Writing your own Hammer plugin
4
4
  In this tutorial we will create a simple hello world plugin.
5
5
 
6
6
  Hammer plugins are nothing but gems. Details on how to build a gem can be found for example at [rubygems.org](http://guides.rubygems.org/make-your-own-gem/).
7
- In the first part of this tutorial we will briefly guide you through the process of creating a very simple gem. First of all you will need rubygems package installed on your system.
7
+ In the first part of this tutorial we will briefly guide you through the process of creating a very simple gem. First of all you will need the rubygems package installed on your system.
8
8
 
9
9
  Create the basic gem structure in a project subdirectory of your choice:
10
10
  ```
@@ -44,7 +44,7 @@ end
44
44
  ```
45
45
  More details about the gemspec structure is again at [rubygems.org](http://guides.rubygems.org/specification-reference/).
46
46
 
47
- We'll have to specify the plugins version in `lib/hammer_cli_hello/version.rb`:
47
+ We'll have to specify the plugin version in `lib/hammer_cli_hello/version.rb`:
48
48
  ```ruby
49
49
  module HammerCLIHello
50
50
  def self.version
@@ -59,12 +59,10 @@ $ gem build ./hammer_cli_hello.gemspec
59
59
  $ gem install hammer_cli_hello-0.0.1.gem
60
60
  ```
61
61
 
62
- Update the hammer config to enable your plugin.
62
+ Place your module's config file into `~/.hammer/cli.modules.d/`.
63
63
  ```yaml
64
- :modules:
65
- - hammer_cli_hello
66
- # - hammer_cli_foreman
67
- # - hammer_cli_katello_bridge
64
+ :hello:
65
+ :enable_module: true
68
66
  ```
69
67
 
70
68
 
@@ -75,12 +73,14 @@ $ hammer -v > /dev/null
75
73
 
76
74
  You should see a message saying that your module was loaded (second line in the sample output).
77
75
  ```
78
- [ INFO 2013-10-16 11:19:06 Init] Configuration from the file /etc/foreman/cli_config.yml has been loaded
76
+ [ INFO 2013-10-16 11:19:06 Init] Initialization of Hammer CLI (0.1.0) has started...
79
77
  [ INFO 2013-10-16 11:19:06 Init] Extension module hammer_cli_hello loaded
78
+ [ INFO 2013-10-16 11:19:06 Init] Configuration from the file /root/.hammer/cli_config.yml has been loaded
79
+ [ INFO 2013-10-16 11:19:06 Init] Configuration from the file /root/.hammer/cli.modiles.d/hello.yml has been loaded
80
80
  [ INFO 2013-10-16 11:19:06 HammerCLI::MainCommand] Called with options: {"verbose"=>true}
81
81
  ```
82
82
 
83
- Done. Your first hammer plugin is installed. Unfortunatelly it does not contain any commands yet. So let's start adding some to finally enjoy real results.
83
+ Done. Your first hammer plugin is installed. Unfortunately it does not contain any commands yet. So let's start adding some to finally enjoy real results.
84
84
 
85
85
  Optionally you can add a Rakefile and build and install the gem with `rake install`
86
86
  ```ruby
@@ -10,6 +10,7 @@ require 'hammer_cli/validator'
10
10
  require 'hammer_cli/output'
11
11
  require 'hammer_cli/options/normalizers'
12
12
  require 'hammer_cli/completer'
13
+ require 'hammer_cli/option_builder'
13
14
  require 'hammer_cli/abstract'
14
15
  require 'hammer_cli/main'
15
16
 
@@ -1,7 +1,7 @@
1
1
  require 'hammer_cli/exception_handler'
2
2
  require 'hammer_cli/logger_watch'
3
3
  require 'hammer_cli/options/option_definition'
4
- require 'clamp'
4
+ require 'hammer_cli/clamp'
5
5
  require 'logging'
6
6
 
7
7
  module HammerCLI
@@ -134,6 +134,26 @@ module HammerCLI
134
134
  HammerCLI.interactive?
135
135
  end
136
136
 
137
+ def self.option_builder
138
+ @option_builder ||= OptionBuilderContainer.new
139
+ @option_builder.builders = custom_option_builders
140
+ @option_builder
141
+ end
142
+
143
+ def self.custom_option_builders
144
+ []
145
+ end
146
+
147
+ def self.build_options(builder_params={})
148
+ option_builder.build(builder_params).each do |option|
149
+ # skip switches that are already defined
150
+ next if option.nil? or option.switches.any? {|s| find_option(s) }
151
+
152
+ declared_options << option
153
+ block ||= option.default_conversion_block
154
+ define_accessors_for(option, &block)
155
+ end
156
+ end
137
157
 
138
158
  protected
139
159
 
@@ -207,16 +227,9 @@ module HammerCLI
207
227
  end
208
228
 
209
229
  def self.option(switches, type, description, opts = {}, &block)
210
- formatter = opts.delete(:format)
211
- context_target = opts.delete(:context_target)
212
-
213
230
  HammerCLI::Options::OptionDefinition.new(switches, type, description, opts).tap do |option|
214
231
  declared_options << option
215
-
216
- option.value_formatter = formatter
217
- option.context_target = context_target
218
232
  block ||= option.default_conversion_block
219
-
220
233
  define_accessors_for(option, &block)
221
234
  end
222
235
  end
@@ -1,3 +1,2 @@
1
+ require File.join(File.dirname(__FILE__), './apipie/option_builder')
1
2
  require File.join(File.dirname(__FILE__), './apipie/command')
2
- require File.join(File.dirname(__FILE__), './apipie/read_command')
3
- require File.join(File.dirname(__FILE__), './apipie/write_command')
@@ -1,4 +1,5 @@
1
1
  require File.join(File.dirname(__FILE__), '../abstract')
2
+ require File.join(File.dirname(__FILE__), '../messages')
2
3
  require File.join(File.dirname(__FILE__), 'options')
3
4
  require File.join(File.dirname(__FILE__), 'resource')
4
5
 
@@ -8,24 +9,7 @@ module HammerCLI::Apipie
8
9
 
9
10
  include HammerCLI::Apipie::Resource
10
11
  include HammerCLI::Apipie::Options
11
-
12
- def self.identifiers(*keys)
13
- @identifiers ||= {}
14
- keys.each do |key|
15
- if key.is_a? Hash
16
- @identifiers.merge!(key)
17
- else
18
- @identifiers.update(key => HammerCLI.option_accessor_name(key))
19
- end
20
- end
21
- end
22
-
23
- def validate_options
24
- super
25
- if self.class.declared_identifiers
26
- validator.any(*self.class.declared_identifiers.values).required
27
- end
28
- end
12
+ include HammerCLI::Messages
29
13
 
30
14
  def self.desc(desc=nil)
31
15
  super(desc) || resource.action(action).apidoc[:apis][0][:short_description] || " "
@@ -33,67 +17,56 @@ module HammerCLI::Apipie
33
17
  " "
34
18
  end
35
19
 
36
- protected
20
+ def self.custom_option_builders
21
+ builders = super
22
+ builders += [
23
+ OptionBuilder.new(resource.action(action), :require_options => false)
24
+ ] if resource_defined?
25
+ builders
26
+ end
37
27
 
38
- def get_identifier
39
- self.class.declared_identifiers.keys.each do |identifier|
40
- value = find_option("--"+identifier.to_s).of(self).read
41
- return [value, identifier] if value
42
- end
43
- [nil, nil]
28
+ def self.apipie_options(*args)
29
+ self.build_options(*args)
44
30
  end
45
31
 
46
- def self.identifier?(key)
47
- if @identifiers
48
- return true if @identifiers.keys.include? key
49
- else
50
- return true if superclass.respond_to?(:identifier?, true) and superclass.identifier?(key)
51
- end
52
- return false
32
+ def execute
33
+ d = send_request
34
+ print_data(d)
35
+ return HammerCLI::EX_OK
53
36
  end
54
37
 
55
- def self.declared_identifiers
56
- if @identifiers
57
- return @identifiers
58
- elsif superclass.respond_to?(:declared_identifiers, true)
59
- superclass.declared_identifiers
38
+ protected
39
+
40
+ def send_request
41
+ if resource && resource.has_action?(action)
42
+ resource.call(action, request_params, request_headers)
60
43
  else
61
- {}
44
+ raise HammerCLI::OperationNotSupportedError, "The server does not support such operation."
62
45
  end
63
46
  end
64
47
 
65
- private
66
-
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
48
+ def request_headers
49
+ {}
71
50
  end
72
51
 
73
- def self.identifier_option(name, desc, attr_name)
74
- option_switch = '--'+name.to_s.gsub('_', '-')
52
+ def request_params
53
+ method_options
54
+ end
75
55
 
76
- if name == :id
77
- option option_switch, name.to_s.upcase, desc, :attribute_name => attr_name
78
- else
79
- option option_switch, name.to_s.upcase, desc, :attribute_name => attr_name do |value|
80
- name_to_id(value, name, resource)
81
- end
82
- end
56
+ def print_data(data)
57
+ print_collection(output_definition, data) unless output_definition.empty?
58
+ print_success_message(data) unless success_message.nil?
83
59
  end
84
60
 
85
- def name_to_id(name, option_name, resource)
86
- results = resource.call(:index, :search => "#{option_name} = #{name}")
87
- results = HammerCLIForeman.collection_to_common_format(results)
61
+ def success_message_params(response)
62
+ response
63
+ end
88
64
 
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
96
- results[0]['id']
65
+ def print_success_message(response)
66
+ print_message(
67
+ success_message,
68
+ success_message_params(response)
69
+ )
97
70
  end
98
71
 
99
72
  end
@@ -0,0 +1,69 @@
1
+
2
+ module HammerCLI::Apipie
3
+
4
+ class OptionBuilder
5
+
6
+ def initialize(action, options={})
7
+ @action = action
8
+ @require_options = options[:require_options].nil? ? true : options[:require_options]
9
+ end
10
+
11
+ def build(builder_params={})
12
+ filter = Array(builder_params[:without])
13
+
14
+ options_for_params(@action.params, filter)
15
+ end
16
+
17
+ attr_writer :require_options
18
+ def require_options?
19
+ @require_options
20
+ end
21
+
22
+ protected
23
+
24
+ def options_for_params(params, filter)
25
+ opts = []
26
+ params.each do |p|
27
+ next if filter.include?(p.name) || filter.include?(p.name.to_sym)
28
+ if p.expected_type == :hash
29
+ opts += options_for_params(p.params, filter)
30
+ else
31
+ opts << create_option(p)
32
+ end
33
+ end
34
+ opts
35
+ end
36
+
37
+ def create_option(param)
38
+ HammerCLI::Options::OptionDefinition.new(
39
+ option_switch(param),
40
+ option_type(param),
41
+ option_desc(param),
42
+ option_opts(param)
43
+ )
44
+ end
45
+
46
+ def option_switch(param)
47
+ '--' + param.name.gsub('_', '-')
48
+ end
49
+
50
+ def option_type(param)
51
+ param.name.upcase.gsub('-', '_')
52
+ end
53
+
54
+ def option_desc(param)
55
+ param.description || " "
56
+ end
57
+
58
+ def option_opts(param)
59
+ opts = {}
60
+ opts[:required] = true if (param.required? and require_options?)
61
+ # FIXME: There is a bug in apipie, it does not produce correct expected type for Arrays
62
+ # When it's fixed, we should test param["expected_type"] == "array"
63
+ opts[:format] = HammerCLI::Options::Normalizers::List.new if param.validator.include? "Array"
64
+ return opts
65
+ end
66
+
67
+
68
+ end
69
+ end