hammer_cli 0.0.18 → 0.1.0

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.
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
@@ -0,0 +1,44 @@
1
+ module HammerCLI
2
+
3
+ class AbstractConnector
4
+ def initialize(params={})
5
+ end
6
+ end
7
+
8
+ class Connection
9
+
10
+ def self.drop(name)
11
+ connections.delete(name)
12
+ end
13
+
14
+ def self.drop_all()
15
+ connections.keys.each { |c| drop(c) }
16
+ end
17
+
18
+ def self.create(name, conector_params={}, options={})
19
+ unless connections[name]
20
+ Logging.logger['Connection'].debug "Registered: #{name}"
21
+ connector = options[:connector] || AbstractConnector
22
+
23
+ connections[name] = connector.new(conector_params)
24
+ end
25
+ connections[name]
26
+ end
27
+
28
+ def self.exist?(name)
29
+ !get(name).nil?
30
+ end
31
+
32
+ def self.get(name)
33
+ connections[name]
34
+ end
35
+
36
+ private
37
+
38
+ def self.connections
39
+ @connections_hash ||= {}
40
+ @connections_hash
41
+ end
42
+
43
+ end
44
+ end
@@ -16,6 +16,7 @@ module HammerCLI
16
16
  [Clamp::UsageError, :handle_usage_exception],
17
17
  [RestClient::ResourceNotFound, :handle_not_found],
18
18
  [RestClient::Unauthorized, :handle_unauthorized],
19
+ [ApipieBindings::DocLoadingError, :handle_apipie_docloading_error],
19
20
  ]
20
21
  end
21
22
 
@@ -49,19 +50,20 @@ module HammerCLI
49
50
 
50
51
  def log_full_error(e)
51
52
  backtrace = e.backtrace || []
52
- @logger.error "\n\n#{e.class} (#{e.message}):\n " +
53
+ error = "\n\n#{e.class} (#{e.message}):\n " +
53
54
  backtrace.join("\n ")
54
55
  "\n\n"
56
+ @logger.error error
55
57
  end
56
58
 
57
59
  def handle_general_exception(e)
58
- print_error "Error: " + e.message
60
+ print_error _("Error: %s") % e.message
59
61
  log_full_error e
60
62
  HammerCLI::EX_SOFTWARE
61
63
  end
62
64
 
63
65
  def handle_usage_exception(e)
64
- print_error "Error: %s\n\nSee: '%s --help'" % [e.message, e.command.invocation_path]
66
+ print_error _("Error: %{message}\n\nSee: '%{path} --help'") % {:message => e.message, :path => e.command.invocation_path}
65
67
  log_full_error e
66
68
  HammerCLI::EX_USAGE
67
69
  end
@@ -78,11 +80,20 @@ module HammerCLI
78
80
  end
79
81
 
80
82
  def handle_unauthorized(e)
81
- print_error "Invalid username or password"
83
+ print_error _("Invalid username or password")
82
84
  log_full_error e
83
85
  HammerCLI::EX_UNAUTHORIZED
84
86
  end
85
87
 
88
+ def handle_apipie_docloading_error(e)
89
+ rake_command = "rake apipie:cache"
90
+ print_error _("Could not load API description from the server\n"\
91
+ " - is your server down?\n"\
92
+ " - was \"#{rake_command}\" run on the server when using apipie cache? (typical production settings))\n")
93
+ log_full_error e
94
+ HammerCLI::EX_CONFIG
95
+ end
96
+
86
97
  end
87
98
  end
88
99
 
@@ -0,0 +1,6 @@
1
+ module HammerCLI
2
+
3
+ class OperationNotSupportedError < StandardError; end
4
+ class ModuleLoadingError < StandardError; end
5
+
6
+ end
@@ -0,0 +1,95 @@
1
+ require 'fast_gettext'
2
+ require 'locale'
3
+
4
+ module HammerCLI
5
+ module I18n
6
+
7
+ module AllDomains
8
+ def _(key)
9
+ FastGettext::TranslationMultidomain.D_(key)
10
+ end
11
+
12
+ def n_(*keys)
13
+ FastGettext::TranslationMultidomain.Dn_(*keys)
14
+ end
15
+
16
+ def s_(key, separator=nil)
17
+ FastGettext::TranslationMultidomain.Ds_(key, separator)
18
+ end
19
+
20
+ def ns_(*keys)
21
+ FastGettext::TranslationMultidomain.Dns_(*keys)
22
+ end
23
+ end
24
+
25
+
26
+ class AbstractLocaleDomain
27
+
28
+ def available_locales
29
+ Dir.glob(locale_dir+'/*').select { |f| File.directory? f }.map { |f| File.basename(f) }
30
+ end
31
+
32
+ def translated_files
33
+ []
34
+ end
35
+
36
+ def type
37
+ :mo
38
+ end
39
+
40
+ attr_reader :locale_dir, :domain_name
41
+ end
42
+
43
+
44
+ class LocaleDomain < AbstractLocaleDomain
45
+
46
+ def translated_files
47
+ Dir.glob(File.join(File.dirname(__FILE__), '../**/*.rb'))
48
+ end
49
+
50
+ def locale_dir
51
+ File.join(File.dirname(__FILE__), '../../locale')
52
+ end
53
+
54
+ def domain_name
55
+ 'hammer-cli'
56
+ end
57
+
58
+ end
59
+
60
+
61
+ def self.locale
62
+ lang_variant = Locale.current.to_simple.to_str
63
+ lang = lang_variant.gsub(/_.*/, "")
64
+
65
+ hammer_domain = HammerCLI::I18n::LocaleDomain.new
66
+ if hammer_domain.available_locales.include? lang_variant
67
+ lang_variant
68
+ else
69
+ lang
70
+ end
71
+ end
72
+
73
+
74
+ def self.domains
75
+ @domains ||= []
76
+ @domains
77
+ end
78
+
79
+ def self.add_domain(domain)
80
+ domains << domain
81
+ FastGettext.add_text_domain(domain.domain_name, :path => domain.locale_dir, :type => domain.type, :report_warning => false)
82
+ end
83
+
84
+
85
+ Encoding.default_external='UTF-8' if defined? Encoding
86
+ FastGettext.locale = locale
87
+
88
+ end
89
+ end
90
+
91
+ include FastGettext::Translation
92
+ include HammerCLI::I18n::AllDomains
93
+
94
+ HammerCLI::I18n.add_domain(HammerCLI::I18n::LocaleDomain.new)
95
+
@@ -19,13 +19,13 @@ module HammerCLI
19
19
  pattern = "[%5l %d %c] %m\n"
20
20
  COLOR_LAYOUT = Logging::Layouts::Pattern.new(:pattern => pattern, :color_scheme => 'bright')
21
21
  NOCOLOR_LAYOUT = Logging::Layouts::Pattern.new(:pattern => pattern, :color_scheme => nil)
22
- DEFAULT_LOG_DIR = '/var/log/foreman'
22
+ DEFAULT_LOG_DIR = '/var/log/hammer'
23
23
 
24
24
  log_dir = File.expand_path(HammerCLI::Settings.get(:log_dir) || DEFAULT_LOG_DIR)
25
25
  begin
26
26
  FileUtils.mkdir_p(log_dir, :mode => 0750)
27
27
  rescue Errno::EACCES => e
28
- puts "No permissions to create log dir #{log_dir}"
28
+ puts _("No permissions to create log dir %s") % log_dir
29
29
  end
30
30
 
31
31
  logger = Logging.logger.root
@@ -40,7 +40,7 @@ module HammerCLI
40
40
  # set owner and group (it's ignored if attribute is nil)
41
41
  FileUtils.chown HammerCLI::Settings.get(:log_owner), HammerCLI::Settings.get(:log_group), filename
42
42
  rescue ArgumentError => e
43
- puts "File #{filename} not writeable, won't log anything to file!"
43
+ puts _("File %s not writeable, won't log anything to the file!") % filename
44
44
  end
45
45
 
46
46
  logger.level = HammerCLI::Settings.get(:log_level)
@@ -4,15 +4,16 @@ module HammerCLI
4
4
 
5
5
  class MainCommand < AbstractCommand
6
6
 
7
- option ["-v", "--verbose"], :flag, "be verbose"
8
- option ["-c", "--config"], "CFG_FILE", "path to custom config file"
7
+ option ["-v", "--verbose"], :flag, _("be verbose"), :context_target => :verbose
8
+ option ["-d", "--debug"], :flag, _("show debugging output "), :context_target => :debug
9
+ option ["-c", "--config"], "CFG_FILE", _("path to custom config file")
9
10
 
10
- option ["-u", "--username"], "USERNAME", "username to access the remote system",
11
+ option ["-u", "--username"], "USERNAME", _("username to access the remote system"),
11
12
  :context_target => :username
12
- option ["-p", "--password"], "PASSWORD", "password to access the remote system",
13
+ option ["-p", "--password"], "PASSWORD", _("password to access the remote system"),
13
14
  :context_target => :password
14
15
 
15
- option "--version", :flag, "show version" do
16
+ option "--version", :flag, _("show version") do
16
17
  puts "hammer (%s)" % HammerCLI.version
17
18
  HammerCLI::Modules.names.each do |m|
18
19
  module_version = HammerCLI::Modules.find_by_name(m).version
@@ -21,21 +22,21 @@ module HammerCLI
21
22
  exit(HammerCLI::EX_OK)
22
23
  end
23
24
 
24
- option ["--show-ids"], :flag, "Show ids of associated resources",
25
+ option ["--show-ids"], :flag, _("Show ids of associated resources"),
25
26
  :context_target => :show_ids
26
- option ["--interactive"], "INTERACTIVE", "Explicitly turn interactive mode on/off",
27
+ option ["--interactive"], "INTERACTIVE", _("Explicitly turn interactive mode on/off"),
27
28
  :format => HammerCLI::Options::Normalizers::Bool.new,
28
29
  :context_target => :interactive
29
30
 
30
- option ["--csv"], :flag, "Output as CSV (same as --output=csv)"
31
- option ["--output"], "ADAPTER", "Set output format. One of [%s]" %
31
+ option ["--csv"], :flag, _("Output as CSV (same as --output=csv)")
32
+ option ["--output"], "ADAPTER", _("Set output format. One of [%s]") %
32
33
  HammerCLI::Output::Output.adapters.keys.join(', '),
33
34
  :context_target => :adapter
34
- option ["--csv-separator"], "SEPARATOR", "Character to separate the values",
35
+ option ["--csv-separator"], "SEPARATOR", _("Character to separate the values"),
35
36
  :context_target => :csv_separator
36
37
 
37
38
 
38
- option "--autocomplete", "LINE", "Get list of possible endings" do |line|
39
+ option "--autocomplete", "LINE", _("Get list of possible endings") do |line|
39
40
  # get rid of word 'hammer' on the line
40
41
  line = line.to_s.gsub(/^\S+/, '')
41
42
 
@@ -4,7 +4,22 @@ module HammerCLI
4
4
  class Modules
5
5
 
6
6
  def self.names
7
- HammerCLI::Settings.get(:modules) || []
7
+
8
+ # legacy modules config
9
+ modules = HammerCLI::Settings.get(:modules) || []
10
+ logger.warn _("Legacy configuration of modules detected. Check section about configuration in user manual") unless modules.empty?
11
+
12
+ HammerCLI::Settings.dump.inject(modules) do |names, (mod_name, mod_config)|
13
+ if mod_config.kind_of?(Hash) && !mod_config[:enable_module].nil?
14
+ mod = ["hammer_cli_#{mod_name}"]
15
+ if mod_config[:enable_module]
16
+ names += mod
17
+ else
18
+ names -= mod # disable when enabled in legacy config
19
+ end
20
+ end
21
+ names
22
+ end
8
23
  end
9
24
 
10
25
  def self.find_by_name(name)
@@ -22,13 +37,11 @@ module HammerCLI
22
37
  def self.load!(name)
23
38
  begin
24
39
  require_module(name)
25
- rescue LoadError => e
26
- logger.error "Module #{name} not found"
27
- raise e
28
40
  rescue Exception => e
29
41
  logger.error "Error while loading module #{name}"
30
- logger.error e
31
- puts "Warning: An error occured while loading module #{name}"
42
+ puts _("Warning: An error occured while loading module %s") % name
43
+ # with ModuleLoadingError we assume the error is already logged by the issuer
44
+ logger.error e unless e.is_a?(HammerCLI::ModuleLoadingError)
32
45
  raise e
33
46
  end
34
47
 
@@ -1,3 +1,5 @@
1
+ require 'json'
2
+
1
3
  module HammerCLI
2
4
  module Options
3
5
  module Normalizers
@@ -21,7 +23,7 @@ module HammerCLI
21
23
  class KeyValueList < AbstractNormalizer
22
24
 
23
25
  def description
24
- "Comma-separated list of key=value."
26
+ _("Comma-separated list of key=value.")
25
27
  end
26
28
 
27
29
  def format(val)
@@ -30,7 +32,7 @@ module HammerCLI
30
32
  val.split(",").inject({}) do |result, item|
31
33
  parts = item.split("=")
32
34
  if parts.size != 2
33
- raise ArgumentError, "value must be defined as a comma-separated list of key=value"
35
+ raise ArgumentError, _("value must be defined as a comma-separated list of key=value")
34
36
  end
35
37
  result.update(parts[0] => parts[1])
36
38
  end
@@ -41,7 +43,7 @@ module HammerCLI
41
43
  class List < AbstractNormalizer
42
44
 
43
45
  def description
44
- "Comma separated list of values."
46
+ _("Comma separated list of values.")
45
47
  end
46
48
 
47
49
  def format(val)
@@ -53,7 +55,7 @@ module HammerCLI
53
55
  class Bool < AbstractNormalizer
54
56
 
55
57
  def description
56
- "One of true/false, yes/no, 1/0."
58
+ _("One of true/false, yes/no, 1/0.")
57
59
  end
58
60
 
59
61
  def format(bool)
@@ -63,7 +65,7 @@ module HammerCLI
63
65
  elsif bool.downcase.match(/^(false|f|no|n|0)$/i)
64
66
  return false
65
67
  else
66
- raise ArgumentError, "value must be one of true/false, yes/no, 1/0"
68
+ raise ArgumentError, _("value must be one of true/false, yes/no, 1/0")
67
69
  end
68
70
  end
69
71
 
@@ -90,6 +92,23 @@ module HammerCLI
90
92
  end
91
93
  end
92
94
 
95
+ class JSONInput < File
96
+
97
+ def format(val)
98
+ # The JSON input can be either the path to a file whose contents are
99
+ # JSON or a JSON string. For example:
100
+ # /my/path/to/file.json
101
+ # or
102
+ # '{ "units":[ { "name":"zip", "version":"9.0", "inclusion":"false" } ] }')
103
+ json_string = ::File.exist?(::File.expand_path(val)) ? super(val) : val
104
+ ::JSON.parse(json_string)
105
+
106
+ rescue ::JSON::ParserError => e
107
+ raise ArgumentError, _("Unable to parse JSON input")
108
+ end
109
+
110
+ end
111
+
93
112
 
94
113
  class Enum < AbstractNormalizer
95
114
 
@@ -98,14 +117,14 @@ module HammerCLI
98
117
  end
99
118
 
100
119
  def description
101
- "One of %s" % quoted_values
120
+ _("One of %s") % quoted_values
102
121
  end
103
122
 
104
123
  def format(value)
105
124
  if @allowed_values.include? value
106
125
  value
107
126
  else
108
- raise ArgumentError, "value must be one of '%s'" % quoted_values
127
+ raise ArgumentError, _("value must be one of '%s'") % quoted_values
109
128
  end
110
129
  end
111
130
 
@@ -120,6 +139,22 @@ module HammerCLI
120
139
  end
121
140
  end
122
141
 
142
+
143
+ class DateTime < AbstractNormalizer
144
+
145
+ def description
146
+ _("Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format")
147
+ end
148
+
149
+ def format(date)
150
+ raise ArgumentError unless date
151
+ ::DateTime.parse(date).to_s
152
+ rescue ArgumentError
153
+ raise ArgumentError, _("'%s' is not a valid date") % date
154
+ end
155
+ end
156
+
157
+
123
158
  end
124
159
  end
125
160
  end
@@ -59,8 +59,8 @@ module HammerCLI
59
59
  ].compact
60
60
 
61
61
  str = ""
62
- str += "Can be specified multiple times. " if multivalued?
63
- str += "Default: " + default_sources.join(", or ") unless default_sources.empty?
62
+ str += _("Can be specified multiple times. ") if multivalued?
63
+ str += _("Default: ") + default_sources.join(_(", or ")) unless default_sources.empty?
64
64
  str
65
65
  end
66
66
 
@@ -5,4 +5,5 @@ require File.join(File.dirname(__FILE__), 'output/adapter')
5
5
  require File.join(File.dirname(__FILE__), 'output/definition')
6
6
  require File.join(File.dirname(__FILE__), 'output/dsl')
7
7
  require File.join(File.dirname(__FILE__), 'output/record_collection')
8
+ require File.join(File.dirname(__FILE__), 'output/field_filter')
8
9