hammer_cli 0.0.18 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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