hammer_cli 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -5
  3. data/doc/creating_apipie_commands.md +296 -0
  4. data/doc/creating_commands.md +547 -0
  5. data/doc/developer_docs.md +5 -926
  6. data/doc/development_tips.md +30 -0
  7. data/doc/writing_a_plugin.md +90 -0
  8. data/lib/hammer_cli/abstract.rb +31 -11
  9. data/lib/hammer_cli/apipie/resource.rb +14 -6
  10. data/lib/hammer_cli/apipie/write_command.rb +14 -5
  11. data/lib/hammer_cli/exception_handler.rb +7 -4
  12. data/lib/hammer_cli/options/normalizers.rb +27 -0
  13. data/lib/hammer_cli/output/adapter/abstract.rb +8 -8
  14. data/lib/hammer_cli/output/adapter/csv.rb +37 -4
  15. data/lib/hammer_cli/output/adapter/silent.rb +2 -2
  16. data/lib/hammer_cli/output/dsl.rb +3 -1
  17. data/lib/hammer_cli/output/output.rb +24 -19
  18. data/lib/hammer_cli/utils.rb +18 -0
  19. data/lib/hammer_cli/version.rb +1 -1
  20. data/lib/hammer_cli.rb +1 -0
  21. data/test/unit/abstract_test.rb +296 -0
  22. data/test/unit/apipie/command_test.rb +270 -0
  23. data/test/unit/apipie/fake_api.rb +101 -0
  24. data/test/unit/apipie/read_command_test.rb +34 -0
  25. data/test/unit/apipie/write_command_test.rb +38 -0
  26. data/test/unit/exception_handler_test.rb +45 -0
  27. data/test/unit/main_test.rb +47 -0
  28. data/test/unit/options/normalizers_test.rb +148 -0
  29. data/test/unit/options/option_definition_test.rb +43 -0
  30. data/test/unit/output/adapter/abstract_test.rb +96 -0
  31. data/test/unit/output/adapter/base_test.rb +27 -0
  32. data/test/unit/output/adapter/csv_test.rb +75 -0
  33. data/test/unit/output/adapter/table_test.rb +58 -0
  34. data/test/unit/output/definition_test.rb +27 -0
  35. data/test/unit/output/dsl_test.rb +119 -0
  36. data/test/unit/output/fields_test.rb +97 -0
  37. data/test/unit/output/formatters_test.rb +83 -0
  38. data/test/unit/output/output_test.rb +104 -0
  39. data/test/unit/settings_test.rb +106 -0
  40. data/test/unit/test_helper.rb +20 -0
  41. data/test/unit/utils_test.rb +35 -0
  42. data/test/unit/validator_test.rb +142 -0
  43. metadata +112 -35
  44. data/LICENSE +0 -5
  45. data/hammer_cli_complete +0 -13
@@ -0,0 +1,30 @@
1
+ Development Tips
2
+ ----------------
3
+
4
+ ### Local gem modifications
5
+ If you want to modify gems setup for development needs, create a file `Gemfile.local` in the root of your hammer-cli checkout. You can override the setup from `Gemfile` there. This file is git-ignored so you can easily keep your custom tuning.
6
+
7
+ Typical usage is for linking plugins from local checkouts:
8
+ ```ruby
9
+ gem 'hammer_cli_foreman', :path => '../hammer-cli-foreman'
10
+ ```
11
+
12
+ ### Debugging with Pry
13
+ [Pry](https://github.com/pry/pry) is a runtime developer console for ruby.
14
+ It allows for debugging when [Pry Debugger](https://github.com/nixme/pry-debugger) is installed alongside.
15
+
16
+ For basic usage, add following lines to your `Gemfile.local`:
17
+
18
+ ```ruby
19
+ gem 'pry'
20
+ gem 'pry-debugger', :platforms => [:ruby_19]
21
+ ```
22
+
23
+ Then add this line at the place where you want the script to break:
24
+
25
+ ```ruby
26
+ require 'pry'; binding.pry
27
+ ```
28
+
29
+ Pry Debugger supports all expected debugging features.
30
+ See [its documentation](https://github.com/nixme/pry-debugger#pry-debugger-) for details.
@@ -0,0 +1,90 @@
1
+ Writing your own Hammer plugin
2
+ ------------------------------
3
+
4
+ In this tutorial we will create a simple hello world plugin.
5
+
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.
8
+
9
+ Create the basic gem structure in a project subdirectory of your choice:
10
+ ```
11
+ $ cd ./my_first_hammer_plugin/
12
+ $ touch Gemfile
13
+ $ touch hammer_cli_hello.gemspec
14
+ $ mkdir -p lib/hammer_cli_hello
15
+ $ touch lib/hammer_cli_hello.rb
16
+ $ touch lib/hammer_cli_hello/version.rb
17
+ ```
18
+
19
+ Example `Gemfile`:
20
+ ```ruby
21
+ source "https://rubygems.org"
22
+
23
+ gemspec
24
+ ```
25
+
26
+ Example `hammer_cli_hello.gemspec` file:
27
+ ```ruby
28
+ $:.unshift File.expand_path("../lib", __FILE__)
29
+ require "hammer_cli_hello/version"
30
+
31
+ Gem::Specification.new do |s|
32
+
33
+ s.name = "hammer_cli_hello"
34
+ s.authors = ["Me"]
35
+ s.version = HammerCLIHello.version.dup
36
+ s.platform = Gem::Platform::RUBY
37
+ s.summary = %q{Hello world commands for Hammer}
38
+
39
+ s.files = Dir['lib/**/*.rb']
40
+ s.require_paths = ["lib"]
41
+
42
+ s.add_dependency 'hammer_cli', '>= 0.0.6'
43
+ end
44
+ ```
45
+ More details about the gemspec structure is again at [rubygems.org](http://guides.rubygems.org/specification-reference/).
46
+
47
+ We'll have to specify the plugins version in `lib/hammer_cli_hello/version.rb`:
48
+ ```ruby
49
+ module HammerCLIHello
50
+ def self.version
51
+ @version ||= Gem::Version.new '0.0.1'
52
+ end
53
+ end
54
+ ```
55
+
56
+ This should be enough for creating a minimalist gem. Let's build and install it.
57
+ ```
58
+ $ gem build ./hammer_cli_hello.gemspec
59
+ $ gem install hammer_cli_hello-0.0.1.gem
60
+ ```
61
+
62
+ Update the hammer config to enable your plugin.
63
+ ```yaml
64
+ :modules:
65
+ - hammer_cli_hello
66
+ # - hammer_cli_foreman
67
+ # - hammer_cli_katello_bridge
68
+ ```
69
+
70
+
71
+ Verify the installation by running:
72
+ ```
73
+ $ hammer -v > /dev/null
74
+ ```
75
+
76
+ You should see a message saying that your module was loaded (second line in the sample output).
77
+ ```
78
+ [ INFO 2013-10-16 11:19:06 Init] Configuration from the file /etc/foreman/cli_config.yml has been loaded
79
+ [ INFO 2013-10-16 11:19:06 Init] Extension module hammer_cli_hello loaded
80
+ [ INFO 2013-10-16 11:19:06 HammerCLI::MainCommand] Called with options: {"verbose"=>true}
81
+ ```
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.
84
+
85
+ Optionally you can add a Rakefile and build and install the gem with `rake install`
86
+ ```ruby
87
+ # ./Rakefile
88
+ require 'bundler/gem_tasks'
89
+ ```
90
+
@@ -53,7 +53,7 @@ module HammerCLI
53
53
  end
54
54
 
55
55
  def exception_handler
56
- @exception_handler ||= exception_handler_class.new(:context=>context, :adapter=>adapter)
56
+ @exception_handler ||= exception_handler_class.new(:output => output)
57
57
  end
58
58
 
59
59
  def initialize(*args)
@@ -66,16 +66,21 @@ module HammerCLI
66
66
  context[:path][-2]
67
67
  end
68
68
 
69
- def self.subcommand!(name, description, subcommand_class = self, &block)
69
+ def self.remove_subcommand(name)
70
70
  self.recognised_subcommands.delete_if do |sc|
71
71
  if sc.is_called?(name)
72
- Logging.logger[self].info "subcommand #{name} (#{sc.subcommand_class}) replaced with #{name} (#{subcommand_class})"
72
+ logger.info "subcommand #{name} (#{sc.subcommand_class}) was removed."
73
73
  true
74
74
  else
75
75
  false
76
76
  end
77
77
  end
78
+ end
79
+
80
+ def self.subcommand!(name, description, subcommand_class = self, &block)
81
+ remove_subcommand(name)
78
82
  self.subcommand(name, description, subcommand_class, &block)
83
+ logger.info "subcommand #{name} (#{subcommand_class}) was created."
79
84
  end
80
85
 
81
86
  def self.subcommand(name, description, subcommand_class = self, &block)
@@ -89,37 +94,52 @@ module HammerCLI
89
94
  def self.output(definition=nil, &block)
90
95
  dsl = HammerCLI::Output::Dsl.new
91
96
  dsl.build &block if block_given?
92
-
93
97
  output_definition.append definition.fields unless definition.nil?
94
98
  output_definition.append dsl.fields
95
99
  end
96
100
 
101
+ def output
102
+ @output ||= HammerCLI::Output::Output.new(context, :default_adapter => adapter)
103
+ end
104
+
97
105
  def output_definition
98
106
  self.class.output_definition
99
107
  end
100
108
 
109
+ def self.inherited_output_definition
110
+ od = nil
111
+ if superclass.respond_to? :output_definition
112
+ od_super = superclass.output_definition
113
+ od = od_super.dup unless od_super.nil?
114
+ end
115
+ od
116
+ end
117
+
101
118
  def self.output_definition
102
- @output_definition ||= HammerCLI::Output::Definition.new
119
+ @output_definition = @output_definition || inherited_output_definition || HammerCLI::Output::Definition.new
103
120
  @output_definition
104
121
  end
105
122
 
106
123
  protected
107
124
 
108
125
  def print_records(definition, records)
109
- HammerCLI::Output::Output.print_records(definition, records, context,
110
- :adapter => adapter)
126
+ output.print_records(definition, records)
111
127
  end
112
128
 
113
- def print_message(msg)
114
- HammerCLI::Output::Output.print_message(msg, context, :adapter=>adapter)
129
+ def print_message(msg, msg_params={})
130
+ output.print_message(msg, msg_params)
115
131
  end
116
132
 
117
- def logger(name=self.class)
133
+ def self.logger(name=self)
118
134
  logger = Logging.logger[name]
119
135
  logger.extend(HammerCLI::Logger::Watch) if not logger.respond_to? :watch
120
136
  logger
121
137
  end
122
138
 
139
+ def logger(name=self.class)
140
+ self.class.logger(name)
141
+ end
142
+
123
143
  def validator
124
144
  options = self.class.recognised_options.collect{|opt| opt.of(self)}
125
145
  @validator ||= HammerCLI::Validator.new(options)
@@ -147,7 +167,7 @@ module HammerCLI
147
167
 
148
168
  def self.command_name(name=nil)
149
169
  @name = name if name
150
- @name
170
+ @name || (superclass.respond_to?(:command_name) ? superclass.command_name : nil)
151
171
  end
152
172
 
153
173
  def self.autoload_subcommands
@@ -52,7 +52,7 @@ module HammerCLI::Apipie
52
52
 
53
53
  def resource
54
54
  # if the resource definition is not available in this command's class
55
- # try to look it up in parent command's class
55
+ # or its superclass try to look it up in parent command's class
56
56
  if self.class.resource
57
57
  return ResourceInstance.from_definition(self.class.resource, resource_config)
58
58
  else
@@ -74,13 +74,12 @@ module HammerCLI::Apipie
74
74
 
75
75
  module ClassMethods
76
76
 
77
- def resource(resource_class=nil, action=nil)
78
- @api_resource = ResourceDefinition.new(resource_class) unless resource_class.nil?
79
- @api_action = action unless action.nil?
77
+ def class_resource
80
78
  return @api_resource if @api_resource
79
+ return superclass.class_resource if superclass.respond_to? :class_resource
80
+ end
81
81
 
82
- # if the resource definition is not available in this class
83
- # try to look it up in it's enclosing module/class
82
+ def module_resource
84
83
  enclosing_module = self.name.split("::")[0..-2].inject(Object) { |mod, cls| mod.const_get cls }
85
84
 
86
85
  if enclosing_module.respond_to? :resource
@@ -88,6 +87,15 @@ module HammerCLI::Apipie
88
87
  end
89
88
  end
90
89
 
90
+ def resource(resource_class=nil, action=nil)
91
+ @api_resource = ResourceDefinition.new(resource_class) unless resource_class.nil?
92
+ @api_action = action unless action.nil?
93
+
94
+ # if the resource definition is not available in this class
95
+ # try to look it up in it's enclosing module/class
96
+ return class_resource || module_resource
97
+ end
98
+
91
99
  def action(action=nil)
92
100
  @api_action = action unless action.nil?
93
101
  return @api_action if @api_action
@@ -7,16 +7,25 @@ module HammerCLI::Apipie
7
7
  include HammerCLI::Messages
8
8
 
9
9
  def execute
10
- send_request
11
- print_success_message
10
+ response = send_request
11
+ logger.watch "Retrieved data: ", response
12
+ print_success_message(response)
12
13
  return HammerCLI::EX_OK
13
14
  end
14
15
 
15
16
  protected
16
17
 
17
- def print_success_message
18
- msg = success_message
19
- print_message(msg) unless msg.nil?
18
+ def success_message_params(response)
19
+ response
20
+ end
21
+
22
+ def print_success_message(response)
23
+ if success_message
24
+ print_message(
25
+ success_message,
26
+ success_message_params(response)
27
+ )
28
+ end
20
29
  end
21
30
 
22
31
  def send_request
@@ -6,8 +6,7 @@ module HammerCLI
6
6
 
7
7
  def initialize(options={})
8
8
  @logger = Logging.logger['Exception']
9
- @context = options[:context] || {}
10
- @adapter = options[:adapter] || :base
9
+ @output = options[:output]
11
10
  end
12
11
 
13
12
  def mappings
@@ -25,6 +24,10 @@ module HammerCLI
25
24
  raise e
26
25
  end
27
26
 
27
+ def output
28
+ @output || HammerCLI::Output::Output.new
29
+ end
30
+
28
31
  protected
29
32
 
30
33
  def print_error(error)
@@ -32,9 +35,9 @@ module HammerCLI
32
35
  @logger.error error
33
36
 
34
37
  if @options[:heading]
35
- HammerCLI::Output::Output.print_error @options[:heading], error, @context, :adapter => @adapter
38
+ output.print_error(@options[:heading], error)
36
39
  else
37
- HammerCLI::Output::Output.print_error error, nil, @context, :adapter => @adapter
40
+ output.print_error(error)
38
41
  end
39
42
  end
40
43
 
@@ -53,6 +53,7 @@ module HammerCLI
53
53
  end
54
54
 
55
55
  def format(bool)
56
+ bool = bool.to_s
56
57
  if bool.downcase.match(/^(true|t|yes|y|1)$/i)
57
58
  return true
58
59
  elsif bool.downcase.match(/^(false|f|no|n|0)$/i)
@@ -71,6 +72,32 @@ module HammerCLI
71
72
  end
72
73
  end
73
74
 
75
+
76
+ class Enum < AbstractNormalizer
77
+
78
+ def initialize(allowed_values)
79
+ @allowed_values = allowed_values
80
+ end
81
+
82
+ def description
83
+ "One of %s" % quoted_values
84
+ end
85
+
86
+ def format(value)
87
+ if @allowed_values.include? value
88
+ value
89
+ else
90
+ raise ArgumentError, "value must be one of '%s'" % quoted_values
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def quoted_values
97
+ @allowed_values.map { |v| "'#{v}'" }.join(', ')
98
+ end
99
+ end
100
+
74
101
  end
75
102
  end
76
103
  end
@@ -11,27 +11,27 @@ module HammerCLI::Output::Adapter
11
11
  @formatters = HammerCLI::Output::Formatters::FormatterLibrary.new(filter_formatters(formatters))
12
12
  end
13
13
 
14
- def print_message(msg)
15
- puts msg
14
+ def print_message(msg, msg_params={})
15
+ puts msg.format(msg_params)
16
16
  end
17
17
 
18
- def print_error(msg, details=nil)
18
+ def print_error(msg, details=nil, msg_params={})
19
19
  details = details.split("\n") if details.kind_of? String
20
20
 
21
21
  if details
22
22
  indent = " "
23
- $stderr.puts msg+":"
24
- $stderr.puts indent + details.join("\n"+indent)
25
- else
26
- $stderr.puts msg
23
+ msg += ":\n"
24
+ msg += indent + details.join("\n"+indent)
27
25
  end
26
+
27
+ $stderr.puts msg.format(msg_params)
28
28
  end
29
29
 
30
30
  def print_records(fields, data)
31
31
  raise NotImplementedError
32
32
  end
33
33
 
34
- private
34
+ private
35
35
 
36
36
  def filter_formatters(formatters_map)
37
37
  formatters_map ||= {}
@@ -17,14 +17,12 @@ module HammerCLI::Output::Adapter
17
17
  end
18
18
 
19
19
  def print_records(fields, data)
20
- csv_string = CSV.generate(
21
- :col_sep => @context[:csv_separator] || ',',
22
- :encoding => 'utf-8') do |csv|
20
+ csv_string = generate do |csv|
23
21
  # labels
24
22
  csv << fields.select{ |f| !(f.class <= Fields::Id) || @context[:show_ids] }.map { |f| f.label }
25
23
  # data
26
24
  data.each do |d|
27
- csv << fields.inject([]) do |row, f|
25
+ csv << fields.inject([]) do |row, f|
28
26
  unless f.class <= Fields::Id && !@context[:show_ids]
29
27
  value = (f.get_value(d) || '')
30
28
  formatter = @formatters.formatter_for_type(f.class)
@@ -36,6 +34,41 @@ module HammerCLI::Output::Adapter
36
34
  end
37
35
  puts csv_string
38
36
  end
37
+
38
+ def print_message(msg, msg_params={})
39
+ csv_string = generate do |csv|
40
+ id = msg_params["id"] || msg_params[:id]
41
+ name = msg_params["name"] || msg_params[:name]
42
+
43
+ labels = ["Message"]
44
+ data = [msg.format(msg_params)]
45
+
46
+ if id
47
+ labels << "Id"
48
+ data << id
49
+ end
50
+
51
+ if name
52
+ labels << "Name"
53
+ data << name
54
+ end
55
+
56
+ csv << labels
57
+ csv << data
58
+ end
59
+ puts csv_string
60
+ end
61
+
62
+ private
63
+
64
+ def generate(&block)
65
+ CSV.generate(
66
+ :col_sep => @context[:csv_separator] || ',',
67
+ :encoding => 'utf-8',
68
+ &block
69
+ )
70
+ end
71
+
39
72
  end
40
73
 
41
74
  HammerCLI::Output::Output.register_adapter(:csv, CSValues)
@@ -3,10 +3,10 @@ module HammerCLI::Output::Adapter
3
3
 
4
4
  class Silent < Abstract
5
5
 
6
- def print_message(msg)
6
+ def print_message(msg, msg_params={})
7
7
  end
8
8
 
9
- def print_error(msg, details=[])
9
+ def print_error(msg, details=[], msg_params={})
10
10
  end
11
11
 
12
12
  def print_records(fields, data)
@@ -18,7 +18,9 @@ module HammerCLI::Output
18
18
  protected
19
19
 
20
20
  def field(key, label, type=nil, options={}, &block)
21
- options[:path] = current_path.clone << key
21
+ options[:path] = current_path.clone
22
+ options[:path] << key if !key.nil?
23
+
22
24
  options[:label] = label
23
25
  type ||= Fields::DataField
24
26
  custom_field type, options, &block
@@ -1,30 +1,33 @@
1
1
  module HammerCLI::Output
2
2
  class Output
3
3
 
4
- def self.print_message(msg, context, options={})
5
- adapter(options[:adapter], context).print_message(msg.to_s)
4
+ def initialize(context={}, options={})
5
+ self.context = context
6
+ self.default_adapter = options[:default_adapter]
6
7
  end
7
8
 
8
- def self.print_error(msg, details=nil, context={}, options={})
9
- adapter(options[:adapter], context).print_error(msg.to_s, details)
9
+ attr_accessor :default_adapter
10
+
11
+ def print_message(msg, msg_params={})
12
+ adapter.print_message(msg.to_s, msg_params)
10
13
  end
11
14
 
12
- def self.print_records(definition, records, context, options={})
13
- adapter(options[:adapter], context).print_records(definition.fields, [records].flatten(1))
15
+ def print_error(msg, details=nil, msg_params={})
16
+ adapter.print_error(msg.to_s, details, msg_params)
14
17
  end
15
18
 
16
- def self.adapter(req_adapter, context)
17
- if context[:adapter]
18
- adapter_name = context[:adapter].to_sym
19
- else
20
- adapter_name = req_adapter
21
- end
19
+ def print_records(definition, records)
20
+ adapter.print_records(definition.fields, [records].flatten(1))
21
+ end
22
+
23
+ def adapter
24
+ adapter_name = context[:adapter] || default_adapter
22
25
 
23
26
  begin
24
- init_adapter(adapter_name, context)
27
+ init_adapter(adapter_name.to_sym)
25
28
  rescue NameError
26
- Logging.logger[self.name].warn("Required adapter '#{adapter_name}' was not found, using 'base' instead")
27
- init_adapter(:base, context)
29
+ Logging.logger[self.class.name].warn("Required adapter '#{adapter_name}' was not found, using 'base' instead")
30
+ init_adapter(:base)
28
31
  end
29
32
  end
30
33
 
@@ -51,10 +54,12 @@ module HammerCLI::Output
51
54
  end
52
55
 
53
56
  private
54
-
55
- def self.init_adapter(adapter_name, context)
56
- raise NameError unless adapters.has_key? adapter_name
57
- adapters[adapter_name].new(context, formatters)
57
+
58
+ attr_accessor :context
59
+
60
+ def init_adapter(adapter_name)
61
+ raise NameError unless self.class.adapters.has_key? adapter_name
62
+ self.class.adapters[adapter_name].new(context, self.class.formatters)
58
63
  end
59
64
 
60
65
  end
@@ -0,0 +1,18 @@
1
+
2
+ class String
3
+
4
+ # string formatting for ruby 1.8
5
+ def format(params)
6
+ if params.is_a? Hash
7
+ array_params = self.scan(/%[<{]([^>}]*)[>}]/).collect do |name|
8
+ name = name[0]
9
+ params[name.to_s] || params[name.to_sym]
10
+ end
11
+
12
+ self.gsub(/%[<{]([^>}]*)[>}]/, '%') % array_params
13
+ else
14
+ self % params
15
+ end
16
+ end
17
+
18
+ end
@@ -1,5 +1,5 @@
1
1
  module HammerCLI
2
2
  def self.version
3
- @version ||= Gem::Version.new '0.0.9'
3
+ @version ||= Gem::Version.new '0.0.10'
4
4
  end
5
5
  end
data/lib/hammer_cli.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'hammer_cli/utils'
1
2
  require 'hammer_cli/version'
2
3
  require 'hammer_cli/exit_codes'
3
4
  require 'hammer_cli/settings'