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.
- checksums.yaml +4 -4
- data/README.md +3 -314
- data/bin/hammer +45 -6
- data/config/cli.modules.d/module_config_template.yml +4 -0
- data/config/cli_config.template.yml +9 -11
- data/doc/developer_docs.md +1 -0
- data/doc/i18n.md +85 -0
- data/doc/installation.md +321 -0
- data/lib/hammer_cli.rb +3 -0
- data/lib/hammer_cli/abstract.rb +15 -24
- data/lib/hammer_cli/apipie/command.rb +13 -7
- data/lib/hammer_cli/apipie/options.rb +14 -16
- data/lib/hammer_cli/apipie/read_command.rb +6 -1
- data/lib/hammer_cli/apipie/resource.rb +48 -58
- data/lib/hammer_cli/apipie/write_command.rb +5 -1
- data/lib/hammer_cli/completer.rb +77 -21
- data/lib/hammer_cli/connection.rb +44 -0
- data/lib/hammer_cli/exception_handler.rb +15 -4
- data/lib/hammer_cli/exceptions.rb +6 -0
- data/lib/hammer_cli/i18n.rb +95 -0
- data/lib/hammer_cli/logger.rb +3 -3
- data/lib/hammer_cli/main.rb +12 -11
- data/lib/hammer_cli/modules.rb +19 -6
- data/lib/hammer_cli/options/normalizers.rb +42 -7
- data/lib/hammer_cli/options/option_definition.rb +2 -2
- data/lib/hammer_cli/output.rb +1 -0
- data/lib/hammer_cli/output/adapter/abstract.rb +20 -0
- data/lib/hammer_cli/output/adapter/base.rb +49 -78
- data/lib/hammer_cli/output/adapter/csv.rb +5 -5
- data/lib/hammer_cli/output/adapter/table.rb +41 -10
- data/lib/hammer_cli/output/dsl.rb +1 -1
- data/lib/hammer_cli/output/field_filter.rb +21 -0
- data/lib/hammer_cli/output/fields.rb +44 -78
- data/lib/hammer_cli/output/formatters.rb +38 -0
- data/lib/hammer_cli/settings.rb +28 -6
- data/lib/hammer_cli/shell.rb +58 -57
- data/lib/hammer_cli/utils.rb +14 -0
- data/lib/hammer_cli/validator.rb +5 -5
- data/lib/hammer_cli/version.rb +1 -1
- data/locale/Makefile +64 -0
- data/locale/hammer-cli.pot +203 -0
- data/locale/zanata.xml +29 -0
- data/test/unit/apipie/command_test.rb +42 -25
- data/test/unit/apipie/read_command_test.rb +10 -7
- data/test/unit/apipie/write_command_test.rb +9 -8
- data/test/unit/completer_test.rb +206 -21
- data/test/unit/connection_test.rb +68 -0
- data/test/unit/fixtures/apipie/architectures.json +153 -0
- data/test/unit/fixtures/apipie/documented.json +79 -0
- data/test/unit/fixtures/json_input/invalid.json +12 -0
- data/test/unit/fixtures/json_input/valid.json +12 -0
- data/test/unit/history_test.rb +71 -0
- data/test/unit/main_test.rb +9 -0
- data/test/unit/modules_test.rb +22 -6
- data/test/unit/options/field_filter_test.rb +27 -0
- data/test/unit/options/normalizers_test.rb +53 -0
- data/test/unit/output/adapter/base_test.rb +162 -10
- data/test/unit/output/adapter/csv_test.rb +16 -3
- data/test/unit/output/adapter/table_test.rb +97 -13
- data/test/unit/output/dsl_test.rb +74 -6
- data/test/unit/output/fields_test.rb +93 -62
- data/test/unit/output/formatters_test.rb +47 -0
- data/test/unit/settings_test.rb +35 -4
- data/test/unit/utils_test.rb +45 -0
- metadata +85 -4
- 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
|
-
|
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: "
|
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: %
|
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,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
|
+
|
data/lib/hammer_cli/logger.rb
CHANGED
@@ -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/
|
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
|
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
|
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)
|
data/lib/hammer_cli/main.rb
CHANGED
@@ -4,15 +4,16 @@ module HammerCLI
|
|
4
4
|
|
5
5
|
class MainCommand < AbstractCommand
|
6
6
|
|
7
|
-
option ["-v", "--verbose"], :flag, "be verbose"
|
8
|
-
option ["-
|
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
|
|
data/lib/hammer_cli/modules.rb
CHANGED
@@ -4,7 +4,22 @@ module HammerCLI
|
|
4
4
|
class Modules
|
5
5
|
|
6
6
|
def self.names
|
7
|
-
|
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
|
-
|
31
|
-
|
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
|
|
data/lib/hammer_cli/output.rb
CHANGED
@@ -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
|
|