hammer_cli 0.0.12 → 0.0.13

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.
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)