hammer_cli 0.0.12 → 0.0.13

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 367645958584805f4e59145418616c507fd9144a
4
- data.tar.gz: d89c9715a3b0b66dacb21708d878c12e9c542e84
3
+ metadata.gz: c64a799978261a9f78c6ce7642a7cb82473cbb0a
4
+ data.tar.gz: 5893a4bfe3980b8794ea988c3895871625f1de47
5
5
  SHA512:
6
- metadata.gz: 0eb7b32bc92e6d4ebedf52c8e9334ba2acab69808967b02a6e6b92110f936fccc9ede2febfb20e970eaa994c1b05582f83b46b964aef4c2307c2f6f01d4f75d3
7
- data.tar.gz: 28164cbd24f5a912b4de8e97e10c917a2690378750ccfd6125f1e4104ff43bea883408f2fca13b70bcbd6abda325f4470b8607b1c4626303b50d7f9bfec0f4d8
6
+ metadata.gz: 18db270151afec8b1da636d4a3aa9088090c1b4de705102d8ca877c2c5cccbba65acbaa21f12dcbd2abb685f1bb2e684d6324afab555e6172857544c1c704215
7
+ data.tar.gz: 2c860c0406767e951e0d63d11d2c2e4e5c9d992148710110941982f3a0a9e295a8faf0c2983de1a97b5ba864b5f0aec66c851918e561132f99253c6b83e2edee
data/bin/hammer CHANGED
@@ -37,6 +37,9 @@ if preparser.verbose?
37
37
  root_logger.appenders = root_logger.appenders << ::Logging.appenders.stderr(:layout => HammerCLI::Logger::COLOR_LAYOUT)
38
38
  end
39
39
 
40
+ hammer_version = Gem.loaded_specs['hammer_cli'].version.to_s
41
+ logger.info "Initialization of Hammer CLI (#{hammer_version}) has started..."
42
+
40
43
  # log which config was loaded (now when we have logging)
41
44
  HammerCLI::Settings.path_history.each do |path|
42
45
  logger.info "Configuration from the file #{path} has been loaded"
@@ -56,7 +59,8 @@ modules.each do |m|
56
59
  handler.handle_exception(e)
57
60
  exit HammerCLI::EX_SOFTWARE
58
61
  end
59
- logger.info "Extension module #{m} loaded"
62
+ module_version = Gem.loaded_specs[m].version.to_s
63
+ logger.info "Extension module #{m} (#{module_version}) loaded"
60
64
  end
61
65
 
62
66
  exit HammerCLI::MainCommand.run || HammerCLI::EX_OK
@@ -1,4 +1,3 @@
1
- require 'hammer_cli/autocompletion'
2
1
  require 'hammer_cli/exception_handler'
3
2
  require 'hammer_cli/logger_watch'
4
3
  require 'hammer_cli/options/option_definition'
@@ -11,7 +10,6 @@ module HammerCLI
11
10
 
12
11
  class AbstractCommand < Clamp::Command
13
12
 
14
- extend Autocompletion
15
13
  class << self
16
14
  attr_accessor :validation_block
17
15
  end
@@ -25,8 +23,6 @@ module HammerCLI
25
23
  raise "exit code must be integer" unless exit_code.is_a? Integer
26
24
  return exit_code
27
25
  rescue => e
28
- # do not catch Clamp errors
29
- raise if e.class <= Clamp::UsageError || e.class <= Clamp::HelpWanted
30
26
  handle_exception e
31
27
  end
32
28
 
@@ -122,6 +118,34 @@ module HammerCLI
122
118
 
123
119
  protected
124
120
 
121
+ def interactive?
122
+ if context[:interactive].nil?
123
+ return STDOUT.tty? && (HammerCLI::Settings.get(:ui, :interactive) != false)
124
+ else
125
+ return context[:interactive]
126
+ end
127
+ end
128
+
129
+ def ask_username
130
+ ask("Username: ") if interactive?
131
+ end
132
+
133
+ def ask_password
134
+ ask("Password for '%s': " % username) {|q| q.echo = false} if interactive?
135
+ end
136
+
137
+ def username(ask_interactively=true)
138
+ context[:username] ||= ENV['FOREMAN_USERNAME'] || HammerCLI::Settings.get(:foreman, :username)
139
+ context[:username] ||= ask_username if ask_interactively
140
+ context[:username]
141
+ end
142
+
143
+ def password(ask_interactively=true)
144
+ context[:password] ||= ENV['FOREMAN_PASSWORD'] || HammerCLI::Settings.get(:foreman, :password)
145
+ context[:password] ||= ask_password if ask_interactively
146
+ context[:password]
147
+ end
148
+
125
149
  def print_record(definition, record)
126
150
  output.print_record(definition, record)
127
151
  end
@@ -181,13 +205,25 @@ module HammerCLI
181
205
  end
182
206
  end
183
207
 
208
+ def self.define_simple_writer_for(attribute, &block)
209
+ define_method(attribute.write_method) do |value|
210
+ value = instance_exec(value, &block) if block
211
+ if attribute.respond_to?(:context_target) && attribute.context_target
212
+ context[attribute.context_target] = value
213
+ end
214
+ attribute.of(self).set(value)
215
+ end
216
+ end
217
+
184
218
  def self.option(switches, type, description, opts = {}, &block)
185
219
  formatter = opts.delete(:format)
220
+ context_target = opts.delete(:context_target)
186
221
 
187
222
  HammerCLI::Options::OptionDefinition.new(switches, type, description, opts).tap do |option|
188
223
  declared_options << option
189
224
 
190
225
  option.value_formatter = formatter
226
+ option.context_target = context_target
191
227
  block ||= option.default_conversion_block
192
228
 
193
229
  define_accessors_for(option, &block)
@@ -70,8 +70,8 @@ module HammerCLI::Apipie
70
70
  def resource_config
71
71
  config = {}
72
72
  config[:base_url] = HammerCLI::Settings.get(:foreman, :host)
73
- config[:username] = context[:username] || ENV['FOREMAN_USERNAME'] || HammerCLI::Settings.get(:foreman, :username)
74
- config[:password] = context[:password] || ENV['FOREMAN_PASSWORD'] || HammerCLI::Settings.get(:foreman, :password)
73
+ config[:username] = username
74
+ config[:password] = password
75
75
  config
76
76
  end
77
77
 
@@ -0,0 +1,193 @@
1
+ require 'enumerator'
2
+
3
+ module HammerCLI
4
+
5
+ class CompleterLine < Array
6
+
7
+ def initialize(line)
8
+ @line = line
9
+ super(line.split)
10
+ end
11
+
12
+ def finished?
13
+ (@line[-1,1] == " ") || @line.empty?
14
+ end
15
+
16
+ end
17
+
18
+ class Completer
19
+
20
+ def initialize(cmd_class)
21
+ @command = cmd_class
22
+ end
23
+
24
+
25
+ def complete(line)
26
+ line = CompleterLine.new(line)
27
+
28
+ cmd, remaining = find_last_cmd(line)
29
+
30
+ opt, value = option_to_complete(cmd, remaining)
31
+ if opt
32
+ return complete_attribute(opt, value)
33
+ else
34
+ param, value = param_to_complete(cmd, remaining)
35
+ if param
36
+ if remaining.finished?
37
+ return complete_attribute(param, value) + complete_command(cmd, remaining)
38
+ else
39
+ return complete_attribute(param, value)
40
+ end
41
+ else
42
+ return complete_command(cmd, remaining)
43
+ end
44
+
45
+ end
46
+ end
47
+
48
+
49
+ protected
50
+
51
+ def complete_attribute(attribute, value)
52
+ if attribute.respond_to?(:complete)
53
+ filter(attribute.complete(value), value)
54
+ else
55
+ []
56
+ end
57
+ end
58
+
59
+
60
+ def param_to_complete(cmd, line)
61
+ params = cmd.parameters.select do |p|
62
+ (p.attribute_name != 'subcommand_name') and (p.attribute_name != 'subcommand_arguments')
63
+ end
64
+
65
+ return [nil, nil] if params.empty?
66
+
67
+ # select param candidates
68
+ param_candidates = []
69
+ line.reverse.each do |word|
70
+ break if word.start_with?('-')
71
+ param_candidates.unshift(word)
72
+ end
73
+
74
+ param = nil
75
+
76
+ if line.finished?
77
+ # "--option " or "--option xx " or "xx "
78
+ value = nil
79
+ param_index = param_candidates.size
80
+ else
81
+ # "--opt" or "--option xx" or "xx yy"
82
+ value = param_candidates.last
83
+ param_index = param_candidates.size - 1
84
+ end
85
+
86
+ if param_index >= 0
87
+ if params.size > param_index
88
+ param = params[param_index]
89
+ elsif params.last.multivalued?
90
+ param = params.last
91
+ end
92
+ end
93
+
94
+ return [param, value]
95
+ end
96
+
97
+
98
+ def option_to_complete(cmd, line)
99
+ return [nil, nil] if line.empty?
100
+
101
+ if line.finished?
102
+ # 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])
105
+ return [opt, nil] if opt and not opt.flag?
106
+ else
107
+ # we complete the value in the second case
108
+ # "--opt" or "--option xx" or "xx yy"
109
+ opt = cmd.find_option(line[-2])
110
+ return [opt, line[-1]] if opt and not opt.flag?
111
+ end
112
+ return [nil, nil]
113
+ end
114
+
115
+
116
+ def find_last_cmd(line)
117
+ cmd = @command
118
+ subcommands = sub_command_map(cmd)
119
+
120
+ # if the last word is not finished we have to select it's parent
121
+ # -> shorten the line
122
+ words = line.dup
123
+ words.pop unless line.finished?
124
+
125
+ cmd_idx = 0
126
+ words.each_with_index do |word, idx|
127
+ unless word.start_with?('-')
128
+ break unless subcommands.has_key? word
129
+
130
+ cmd = subcommands[word]
131
+ cmd_idx = idx+1
132
+ subcommands = sub_command_map(cmd)
133
+ end
134
+ end
135
+
136
+ # cut processed part of the line and return remaining
137
+ remaining = line.dup
138
+ remaining.shift(cmd_idx) if cmd_idx > 0
139
+ return [cmd, remaining]
140
+ end
141
+
142
+
143
+ def complete_command(command, remaining)
144
+ completions = []
145
+ completions += sub_command_names(command)
146
+ completions += command_options(command)
147
+ completions = Completer::finalize_completions(completions)
148
+
149
+ if remaining.finished?
150
+ return completions
151
+ else
152
+ return filter(completions, remaining.last)
153
+ end
154
+ end
155
+
156
+
157
+ def filter(completions, last_word)
158
+ if last_word.to_s != ""
159
+ completions.select{|name| name.start_with? last_word }
160
+ else
161
+ completions
162
+ end
163
+ end
164
+
165
+
166
+ def self.finalize_completions(completions)
167
+ completions.collect{|name| name+' ' }
168
+ end
169
+
170
+
171
+ def sub_command_map(cmd_class)
172
+ cmd_class.recognised_subcommands.inject({}) do |cmd_map, cmd|
173
+ cmd.names.each do |name|
174
+ cmd_map.update(name => cmd.subcommand_class)
175
+ end
176
+ cmd_map
177
+ end
178
+ end
179
+
180
+
181
+ def sub_command_names(cmd_class)
182
+ sub_command_map(cmd_class).keys.flatten
183
+ end
184
+
185
+
186
+ def command_options(cmd_class)
187
+ cmd_class.recognised_options.inject([]) do |opt_switches, opt|
188
+ opt_switches += opt.switches
189
+ end
190
+ end
191
+
192
+ end
193
+ end
@@ -12,6 +12,8 @@ module HammerCLI
12
12
  def mappings
13
13
  [
14
14
  [Exception, :handle_general_exception], # catch all
15
+ [Clamp::HelpWanted, :handle_help_wanted],
16
+ [Clamp::UsageError, :handle_usage_exception],
15
17
  [RestClient::ResourceNotFound, :handle_not_found],
16
18
  [RestClient::Unauthorized, :handle_unauthorized],
17
19
  ]
@@ -41,6 +43,10 @@ module HammerCLI
41
43
  end
42
44
  end
43
45
 
46
+ def print_message(msg)
47
+ output.print_message(msg)
48
+ end
49
+
44
50
  def log_full_error(e)
45
51
  backtrace = e.backtrace || []
46
52
  @logger.error "\n\n#{e.class} (#{e.message}):\n " +
@@ -54,6 +60,17 @@ module HammerCLI
54
60
  HammerCLI::EX_SOFTWARE
55
61
  end
56
62
 
63
+ def handle_usage_exception(e)
64
+ print_error "Error: %s\n\nSee: '%s --help'" % [e.message, e.command.invocation_path]
65
+ log_full_error e
66
+ HammerCLI::EX_USAGE
67
+ end
68
+
69
+ def handle_help_wanted(e)
70
+ print_message e.command.help
71
+ HammerCLI::EX_OK
72
+ end
73
+
57
74
  def handle_not_found(e)
58
75
  print_error e.message
59
76
  log_full_error e
@@ -7,70 +7,48 @@ module HammerCLI
7
7
  option ["-v", "--verbose"], :flag, "be verbose"
8
8
  option ["-c", "--config"], "CFG_FILE", "path to custom config file"
9
9
 
10
- option ["-u", "--username"], "USERNAME", "username to access the remote system"
11
- option ["-p", "--password"], "PASSWORD", "password to access the remote system"
10
+ option ["-u", "--username"], "USERNAME", "username to access the remote system",
11
+ :context_target => :username
12
+ option ["-p", "--password"], "PASSWORD", "password to access the remote system",
13
+ :context_target => :password
12
14
 
13
15
  option "--version", :flag, "show version" do
14
- puts "hammer-%s" % HammerCLI.version
16
+ puts "hammer (%s)" % HammerCLI.version
17
+ modules = HammerCLI::Settings.get(:modules) || []
18
+ modules.each do |m|
19
+ module_version = Gem.loaded_specs[m].version.to_s
20
+ puts " * #{m} (#{module_version})"
21
+ end
15
22
  exit(HammerCLI::EX_OK)
16
23
  end
17
24
 
18
- option ["--show-ids"], :flag, "Show ids of associated resources"
25
+ option ["--show-ids"], :flag, "Show ids of associated resources",
26
+ :context_target => :show_ids
27
+ option ["--interactive"], "INTERACTIVE", "Explicitly turn interactive mode on/off",
28
+ :format => HammerCLI::Options::Normalizers::Bool.new,
29
+ :context_target => :interactive
19
30
 
20
31
  option ["--csv"], :flag, "Output as CSV (same as --adapter=csv)"
21
- option ["--output"], "ADAPTER", "Set output format. One of [%s]" %
22
- HammerCLI::Output::Output.adapters.keys.join(', ')
23
- option ["--csv-separator"], "SEPARATOR", "Character to separate the values"
32
+ option ["--output"], "ADAPTER", "Set output format. One of [%s]" %
33
+ HammerCLI::Output::Output.adapters.keys.join(', '),
34
+ :context_target => :adapter
35
+ option ["--csv-separator"], "SEPARATOR", "Character to separate the values",
36
+ :context_target => :csv_separator
24
37
 
25
- option ["-P", "--ask-pass"], :flag, "Ask for password" do
26
- context[:password] = get_password()
27
- ''
28
- end
29
38
 
30
39
  option "--autocomplete", "LINE", "Get list of possible endings" do |line|
31
- line = line.split
32
- line.shift
33
- endings = self.class.autocomplete(line).map { |l| l[0] }
34
- puts endings.join(' ')
35
- exit(HammerCLI::EX_OK)
36
- end
37
-
38
- def show_ids=(show_ids)
39
- context[:show_ids] = show_ids
40
- end
40
+ # get rid of word 'hammer' on the line
41
+ line = line.to_s.gsub(/^\S+/, '')
41
42
 
42
- def run(*args)
43
- super
44
- end
45
-
46
- def password=(p)
47
- @password = p
48
- context[:password] = p
43
+ completer = Completer.new(HammerCLI::MainCommand)
44
+ puts completer.complete(line).join(" ")
45
+ exit(HammerCLI::EX_OK)
49
46
  end
50
47
 
51
48
  def csv=(csv)
52
49
  context[:adapter] = :csv
53
50
  end
54
51
 
55
- def csv_separator=(separator)
56
- context[:csv_separator] = separator
57
- end
58
-
59
- def output=(adapter)
60
- context[:adapter] = adapter
61
- end
62
-
63
- def username=(u)
64
- @username = u
65
- context[:username] = u
66
- end
67
-
68
- private
69
-
70
- def get_password(prompt="Enter Password ")
71
- ask(prompt) {|q| q.echo = false}
72
- end
73
-
74
52
  end
75
53
 
76
54
  end
@@ -11,6 +11,10 @@ module HammerCLI
11
11
  def format(val)
12
12
  raise NotImplementedError, "Class #{self.class.name} must implement method format."
13
13
  end
14
+
15
+ def complete(val)
16
+ []
17
+ end
14
18
  end
15
19
 
16
20
 
@@ -62,6 +66,10 @@ module HammerCLI
62
66
  raise ArgumentError, "value must be one of true/false, yes/no, 1/0"
63
67
  end
64
68
  end
69
+
70
+ def complete(value)
71
+ ["yes ", "no "]
72
+ end
65
73
  end
66
74
 
67
75
 
@@ -70,6 +78,16 @@ module HammerCLI
70
78
  def format(path)
71
79
  ::File.read(::File.expand_path(path))
72
80
  end
81
+
82
+ def complete(value)
83
+ Dir[value.to_s+'*'].collect do |file|
84
+ if ::File.directory?(file)
85
+ file+'/'
86
+ else
87
+ file+' '
88
+ end
89
+ end
90
+ end
73
91
  end
74
92
 
75
93
 
@@ -91,6 +109,10 @@ module HammerCLI
91
109
  end
92
110
  end
93
111
 
112
+ def complete(value)
113
+ Completer::finalize_completions(@allowed_values)
114
+ end
115
+
94
116
  private
95
117
 
96
118
  def quoted_values
@@ -6,6 +6,15 @@ module HammerCLI
6
6
  class OptionDefinition < Clamp::Option::Definition
7
7
 
8
8
  attr_accessor :value_formatter
9
+ attr_accessor :context_target
10
+
11
+ def complete(value)
12
+ if value_formatter.nil?
13
+ []
14
+ else
15
+ value_formatter.complete(value)
16
+ end
17
+ end
9
18
 
10
19
  def help_lhs
11
20
  super
@@ -12,26 +12,28 @@ module HammerCLI::Output::Adapter
12
12
  print_collection(fields, [record].flatten(1))
13
13
  end
14
14
 
15
- def print_collection(fields, collection)
15
+ def print_collection(all_fields, collection)
16
+
17
+ fields = all_fields.reject { |f| f.class <= Fields::Id && !@context[:show_ids] }
16
18
 
17
19
  rows = collection.collect do |d|
18
20
  row = {}
19
21
  fields.each do |f|
20
-
21
22
  row[f.label.to_sym] = f.get_value(d) || ""
22
23
  end
23
24
  row
24
25
  end
25
26
 
26
27
  options = fields.collect do |f|
27
- next if f.class <= Fields::Id && !@context[:show_ids]
28
28
  { f.label.to_sym => { :formatters => Array(@formatters.formatter_for_type(f.class)) } }
29
29
  end
30
30
 
31
+ sort_order = fields.map { |f| f.label.upcase }
32
+
31
33
  printer = TablePrint::Printer.new(rows, options)
32
34
  TablePrint::Config.max_width = 40
33
35
 
34
- output = printer.table_print
36
+ output = sort_columns(printer.table_print, sort_order)
35
37
  dashes = /\n([-|]+)\n/.match(output)
36
38
 
37
39
  puts dashes[1] if dashes
@@ -46,6 +48,42 @@ module HammerCLI::Output::Adapter
46
48
  puts '-' * size
47
49
  end
48
50
 
51
+ private
52
+
53
+ def sort_columns(output, sort_order)
54
+ return output if sort_order.length == 1 # don't sort one column
55
+ delimiter = ' | '
56
+ lines = output.split("\n")
57
+ out = []
58
+
59
+ headers = lines.first.split(delimiter).map(&:strip)
60
+
61
+ # create mapping table for column indexes
62
+ sort_map = []
63
+ sort_order.each { |c| sort_map << headers.index(c) }
64
+
65
+ lines.each do |line|
66
+ columns = line.split(delimiter)
67
+ if columns.length == 1 # dashes
68
+ columns = columns.first.split('-|-')
69
+ if columns.length == 1
70
+ out << columns.first
71
+ else # new style dashes
72
+ new_row = []
73
+ sort_map.each { |i| new_row << columns[i] }
74
+ out << new_row.join('-|-')
75
+ end
76
+ else
77
+ # reorder row
78
+ new_row = []
79
+ sort_map.each { |i| new_row << columns[i] }
80
+ out << new_row.join(delimiter)
81
+ end
82
+ end
83
+
84
+ out.join("\n")
85
+ end
86
+
49
87
  end
50
88
 
51
89
  HammerCLI::Output::Output.register_adapter(:table, Table)