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.
- 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
@@ -35,6 +35,26 @@ module HammerCLI::Output::Adapter
|
|
35
35
|
raise NotImplementedError
|
36
36
|
end
|
37
37
|
|
38
|
+
protected
|
39
|
+
|
40
|
+
def field_filter
|
41
|
+
HammerCLI::Output::FieldFilter.new
|
42
|
+
end
|
43
|
+
|
44
|
+
def data_for_field(field, record)
|
45
|
+
path = field.path
|
46
|
+
|
47
|
+
path.inject(record) do |record, path_key|
|
48
|
+
if record.has_key? path_key.to_sym
|
49
|
+
record[path_key.to_sym]
|
50
|
+
elsif record.has_key? path_key.to_s
|
51
|
+
record[path_key.to_s]
|
52
|
+
else
|
53
|
+
return nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
38
58
|
private
|
39
59
|
|
40
60
|
def filter_formatters(formatters_map)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module HammerCLI::Output::Adapter
|
2
2
|
class Base < Abstract
|
3
3
|
|
4
|
-
GROUP_INDENT = " "*
|
4
|
+
GROUP_INDENT = " "*4
|
5
5
|
LABEL_DIVIDER = ": "
|
6
6
|
|
7
7
|
def tags
|
@@ -13,117 +13,88 @@ module HammerCLI::Output::Adapter
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def print_collection(fields, collection)
|
16
|
-
|
17
|
-
|
18
|
-
collection.each do |d|
|
19
|
-
fields.collect do |f|
|
20
|
-
render_field(f, d)
|
21
|
-
end
|
16
|
+
collection.each do |data|
|
17
|
+
puts render_fields(fields, data)
|
22
18
|
puts
|
23
19
|
end
|
24
20
|
end
|
25
21
|
|
26
22
|
protected
|
27
23
|
|
28
|
-
def
|
29
|
-
|
24
|
+
def field_filter
|
25
|
+
filtered = []
|
26
|
+
filtered << Fields::Id unless @context[:show_ids]
|
27
|
+
HammerCLI::Output::FieldFilter.new(filtered)
|
30
28
|
end
|
31
29
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
field.output_definition.fields.collect do |f|
|
38
|
-
render_field(f, data, indent)
|
30
|
+
def filter_fields(fields, data)
|
31
|
+
field_filter.filter(fields).reject do |field|
|
32
|
+
field_data = data_for_field(field, data)
|
33
|
+
not field.display?(field_data)
|
39
34
|
end
|
40
35
|
end
|
41
36
|
|
42
|
-
def
|
43
|
-
|
44
|
-
puts format_value(field, data).to_s
|
45
|
-
end
|
37
|
+
def render_fields(fields, data)
|
38
|
+
output = ""
|
46
39
|
|
47
|
-
|
48
|
-
render_label(field, indent)
|
49
|
-
params = field.get_value(data) || []
|
50
|
-
print "%{name} => %{value}" % params
|
51
|
-
end
|
40
|
+
fields = filter_fields(fields, data)
|
52
41
|
|
53
|
-
|
54
|
-
render_label(field, indent)
|
55
|
-
puts
|
56
|
-
indent = indent.to_s + " "*(label_width_for_list(self.fields)+LABEL_DIVIDER.size)
|
42
|
+
label_width = label_width(fields)
|
57
43
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
44
|
+
fields.collect do |field|
|
45
|
+
field_data = data_for_field(field, data)
|
46
|
+
|
47
|
+
next unless field.display?(field_data)
|
48
|
+
output += render_field(field, field_data, label_width)
|
49
|
+
output += "\n"
|
64
50
|
end
|
51
|
+
output.rstrip
|
65
52
|
end
|
66
53
|
|
67
|
-
def
|
68
|
-
render_label(field, indent)
|
69
|
-
data = field.get_value(data)
|
70
|
-
puts field.attributes.collect{|attr| data[attr] }.join(" ")
|
71
|
-
end
|
54
|
+
def render_field(field, data, label_width)
|
72
55
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
56
|
+
if field.is_a? Fields::ContainerField
|
57
|
+
output = ""
|
58
|
+
|
59
|
+
idx = 0
|
60
|
+
data = [data] unless data.is_a? Array
|
61
|
+
data.each do |d|
|
62
|
+
idx += 1
|
63
|
+
fields_output = render_fields(field.fields, d).indent_with(GROUP_INDENT)
|
64
|
+
fields_output = fields_output.sub(/^[ ]{4}/, " %-3s" % "#{idx})") if field.is_a? Fields::Collection
|
80
65
|
|
81
|
-
|
82
|
-
|
83
|
-
|
66
|
+
output += fields_output
|
67
|
+
output += "\n"
|
68
|
+
end
|
69
|
+
|
70
|
+
render_label(field, label_width) + "\n" + output.rstrip
|
71
|
+
else
|
72
|
+
render_label(field, label_width) +
|
73
|
+
render_value(field, data)
|
74
|
+
end
|
84
75
|
end
|
85
76
|
|
86
|
-
def render_label(field,
|
77
|
+
def render_label(field, width)
|
87
78
|
if field.label
|
88
|
-
|
89
|
-
print indent.to_s+"%#{-w}s" % (field.label.to_s+LABEL_DIVIDER)
|
79
|
+
"%-#{width}s" % (field.label.to_s + LABEL_DIVIDER)
|
90
80
|
else
|
91
|
-
|
81
|
+
""
|
92
82
|
end
|
93
83
|
end
|
94
84
|
|
95
|
-
def
|
96
|
-
format_method = "format_"+field_type(field.class)
|
97
|
-
|
98
|
-
value = field.get_value(data)
|
85
|
+
def render_value(field, data)
|
99
86
|
formatter = @formatters.formatter_for_type(field.class)
|
100
|
-
|
101
|
-
|
102
|
-
end
|
103
|
-
|
104
|
-
def field_type(field_class)
|
105
|
-
field_class.name.split("::")[-1]
|
106
|
-
end
|
107
|
-
|
108
|
-
def label_width_for(field)
|
109
|
-
if field.respond_to?(:output_definition)
|
110
|
-
label_width_for_list(field.output_definition.fields)+GROUP_INDENT.size
|
111
|
-
elsif field.respond_to?(:label)
|
112
|
-
field.label.size+LABEL_DIVIDER.size rescue 0
|
113
|
-
else
|
114
|
-
0
|
115
|
-
end
|
87
|
+
data = formatter.format(data) if formatter
|
88
|
+
data.to_s
|
116
89
|
end
|
117
90
|
|
118
|
-
def
|
91
|
+
def label_width(fields)
|
119
92
|
fields.inject(0) do |result, f|
|
120
|
-
width =
|
93
|
+
width = f.label.to_s.size + LABEL_DIVIDER.size
|
121
94
|
(width > result) ? width : result
|
122
95
|
end
|
123
96
|
end
|
124
97
|
|
125
|
-
attr_accessor :fields
|
126
|
-
|
127
98
|
end
|
128
99
|
|
129
100
|
HammerCLI::Output::Output.register_adapter(:base, Base)
|
@@ -28,9 +28,9 @@ module HammerCLI::Output::Adapter
|
|
28
28
|
collection.each do |d|
|
29
29
|
csv << fields.inject([]) do |row, f|
|
30
30
|
unless f.class <= Fields::Id && !@context[:show_ids]
|
31
|
-
value = (f
|
31
|
+
value = data_for_field(f, d)
|
32
32
|
formatter = @formatters.formatter_for_type(f.class)
|
33
|
-
row << (formatter ? formatter.format(value) : value)
|
33
|
+
row << ((formatter ? formatter.format(value) : value) || '')
|
34
34
|
end
|
35
35
|
row
|
36
36
|
end
|
@@ -44,16 +44,16 @@ module HammerCLI::Output::Adapter
|
|
44
44
|
id = msg_params["id"] || msg_params[:id]
|
45
45
|
name = msg_params["name"] || msg_params[:name]
|
46
46
|
|
47
|
-
labels = ["Message"]
|
47
|
+
labels = [_("Message")]
|
48
48
|
data = [msg.format(msg_params)]
|
49
49
|
|
50
50
|
if id
|
51
|
-
labels << "Id"
|
51
|
+
labels << _("Id")
|
52
52
|
data << id
|
53
53
|
end
|
54
54
|
|
55
55
|
if name
|
56
|
-
labels << "Name"
|
56
|
+
labels << _("Name")
|
57
57
|
data << name
|
58
58
|
end
|
59
59
|
|
@@ -4,6 +4,9 @@ module HammerCLI::Output::Adapter
|
|
4
4
|
|
5
5
|
class Table < Abstract
|
6
6
|
|
7
|
+
MAX_COLUMN_WIDTH = 80
|
8
|
+
MIN_COLUMN_WIDTH = 5
|
9
|
+
|
7
10
|
def tags
|
8
11
|
[:screen, :flat]
|
9
12
|
end
|
@@ -13,25 +16,29 @@ module HammerCLI::Output::Adapter
|
|
13
16
|
end
|
14
17
|
|
15
18
|
def print_collection(all_fields, collection)
|
16
|
-
|
17
|
-
fields = all_fields.reject { |f| f.class <= Fields::Id && !@context[:show_ids] }
|
19
|
+
fields = field_filter.filter(all_fields)
|
18
20
|
|
19
21
|
rows = collection.collect do |d|
|
20
22
|
row = {}
|
21
23
|
fields.each do |f|
|
22
|
-
row[f
|
24
|
+
row[label_for(f)] = data_for_field(f, d) || ""
|
23
25
|
end
|
24
26
|
row
|
25
27
|
end
|
26
28
|
|
27
29
|
options = fields.collect do |f|
|
28
|
-
{ f
|
30
|
+
{ label_for(f) => {
|
31
|
+
:formatters => Array(@formatters.formatter_for_type(f.class)),
|
32
|
+
:width => max_width_for(f)
|
33
|
+
}
|
34
|
+
}
|
29
35
|
end
|
30
36
|
|
31
37
|
sort_order = fields.map { |f| f.label.upcase }
|
32
38
|
|
33
39
|
printer = TablePrint::Printer.new(rows, options)
|
34
|
-
TablePrint::Config.max_width =
|
40
|
+
TablePrint::Config.max_width = MAX_COLUMN_WIDTH
|
41
|
+
TablePrint::Config.multibyte = true
|
35
42
|
|
36
43
|
output = sort_columns(printer.table_print, sort_order)
|
37
44
|
dashes = /\n([-|]+)\n/.match(output)
|
@@ -41,15 +48,39 @@ module HammerCLI::Output::Adapter
|
|
41
48
|
puts dashes[1] if dashes
|
42
49
|
end
|
43
50
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
51
|
+
protected
|
52
|
+
|
53
|
+
def field_filter
|
54
|
+
filtered = [Fields::ContainerField]
|
55
|
+
filtered << Fields::Id unless @context[:show_ids]
|
56
|
+
HammerCLI::Output::FieldFilter.new(filtered)
|
49
57
|
end
|
50
58
|
|
51
59
|
private
|
52
60
|
|
61
|
+
def label_for(field)
|
62
|
+
width = width_for(field)
|
63
|
+
if width
|
64
|
+
"%-#{width}s" % field.label.to_s
|
65
|
+
else
|
66
|
+
field.label.to_s
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def max_width_for(field)
|
71
|
+
width = width_for(field)
|
72
|
+
width ||= field.parameters[:max_width]
|
73
|
+
width = MIN_COLUMN_WIDTH if width && width < MIN_COLUMN_WIDTH
|
74
|
+
width
|
75
|
+
end
|
76
|
+
|
77
|
+
def width_for(field)
|
78
|
+
width = field.parameters[:width]
|
79
|
+
width = MIN_COLUMN_WIDTH if width && width < MIN_COLUMN_WIDTH
|
80
|
+
width
|
81
|
+
end
|
82
|
+
|
83
|
+
|
53
84
|
def sort_columns(output, sort_order)
|
54
85
|
return output if sort_order.length == 1 # don't sort one column
|
55
86
|
delimiter = ' | '
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module HammerCLI::Output
|
2
|
+
|
3
|
+
class FieldFilter
|
4
|
+
|
5
|
+
def initialize(field_classes=[])
|
6
|
+
@field_classes = field_classes
|
7
|
+
end
|
8
|
+
|
9
|
+
def filter(fields)
|
10
|
+
fields = fields.clone
|
11
|
+
@field_classes.each do |cls|
|
12
|
+
fields.reject! do |f|
|
13
|
+
f.is_a? cls
|
14
|
+
end
|
15
|
+
end
|
16
|
+
fields
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -4,93 +4,42 @@ module Fields
|
|
4
4
|
|
5
5
|
class Field
|
6
6
|
|
7
|
-
def initialize(options={})
|
8
|
-
end
|
9
|
-
|
10
|
-
def get_value(data)
|
11
|
-
end
|
12
|
-
|
13
|
-
end
|
14
|
-
|
15
|
-
class LabeledField < Field
|
16
|
-
|
17
7
|
attr_reader :label
|
18
|
-
|
19
|
-
def initialize(options={})
|
20
|
-
@label = options[:label]
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
class DataField < LabeledField
|
25
|
-
|
26
8
|
attr_reader :path
|
27
9
|
|
28
10
|
def initialize(options={})
|
11
|
+
@hide_blank = options[:hide_blank].nil? ? false : options[:hide_blank]
|
29
12
|
@path = options[:path] || []
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
def get_value(data)
|
34
|
-
follow_path(data, path || [])
|
13
|
+
@label = options[:label]
|
14
|
+
@options = options
|
35
15
|
end
|
36
16
|
|
37
|
-
|
38
|
-
|
39
|
-
def follow_path(record, path)
|
40
|
-
record = symbolize_hash_keys(record)
|
41
|
-
|
42
|
-
path.inject(record) do |record, path_key|
|
43
|
-
if record.has_key? path_key.to_sym
|
44
|
-
record[path_key.to_sym]
|
45
|
-
else
|
46
|
-
return nil
|
47
|
-
end
|
48
|
-
end
|
17
|
+
def hide_blank?
|
18
|
+
@hide_blank
|
49
19
|
end
|
50
20
|
|
51
|
-
def
|
52
|
-
if
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
result
|
57
|
-
end
|
58
|
-
elsif h.is_a? Array
|
59
|
-
return h.collect{|item| symbolize_hash_keys(item)}
|
21
|
+
def display?(value)
|
22
|
+
if not hide_blank?
|
23
|
+
true
|
24
|
+
elsif value.nil?
|
25
|
+
false
|
60
26
|
else
|
61
|
-
|
27
|
+
true
|
62
28
|
end
|
63
29
|
end
|
64
30
|
|
65
|
-
|
66
|
-
|
67
|
-
class Date < DataField
|
68
|
-
end
|
69
|
-
|
70
|
-
class Id < DataField
|
71
|
-
end
|
72
|
-
|
73
|
-
class List < DataField
|
74
|
-
end
|
75
|
-
|
76
|
-
class KeyValue < DataField
|
77
|
-
end
|
78
|
-
|
79
|
-
|
80
|
-
class Joint < DataField
|
81
|
-
def initialize(options={}, &block)
|
82
|
-
super(options)
|
83
|
-
@attributes = options[:attributes] || []
|
31
|
+
def parameters
|
32
|
+
@options
|
84
33
|
end
|
85
34
|
|
86
|
-
attr_reader :attributes
|
87
35
|
end
|
88
36
|
|
89
|
-
|
37
|
+
|
38
|
+
class ContainerField < Field
|
90
39
|
|
91
40
|
def initialize(options={}, &block)
|
92
41
|
super(options)
|
93
|
-
dsl = HammerCLI::Output::Dsl.new
|
42
|
+
dsl = HammerCLI::Output::Dsl.new
|
94
43
|
dsl.build &block if block_given?
|
95
44
|
|
96
45
|
self.output_definition.append dsl.fields
|
@@ -101,23 +50,40 @@ module Fields
|
|
101
50
|
@output_definition
|
102
51
|
end
|
103
52
|
|
53
|
+
def fields
|
54
|
+
@output_definition.fields
|
55
|
+
end
|
56
|
+
|
57
|
+
def display?(value)
|
58
|
+
if not hide_blank?
|
59
|
+
true
|
60
|
+
elsif value.nil? || value.empty?
|
61
|
+
false
|
62
|
+
else
|
63
|
+
true
|
64
|
+
end
|
65
|
+
end
|
104
66
|
end
|
105
67
|
|
106
|
-
class
|
68
|
+
class Date < Field
|
69
|
+
end
|
107
70
|
|
108
|
-
|
109
|
-
|
110
|
-
dsl = HammerCLI::Output::Dsl.new
|
111
|
-
dsl.build &block if block_given?
|
71
|
+
class Id < Field
|
72
|
+
end
|
112
73
|
|
113
|
-
|
114
|
-
|
74
|
+
class List < Field
|
75
|
+
end
|
115
76
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
77
|
+
class LongText < Field
|
78
|
+
end
|
79
|
+
|
80
|
+
class KeyValue < Field
|
81
|
+
end
|
82
|
+
|
83
|
+
class Label < ContainerField
|
84
|
+
end
|
120
85
|
|
86
|
+
class Collection < ContainerField
|
121
87
|
end
|
122
88
|
|
123
89
|
end
|