hammer_cli 0.17.1 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 541191afc24c45034d93c4c8adb074510ecc29dbf511becc8b1ba0ef4b2cac6c
4
- data.tar.gz: 33e1685510fc9e0e4c7d8dafe1a9e78ccbd08f73008681dc8686a5d802e4b2af
3
+ metadata.gz: eb040c3f40863eea4ec0c05ac2d1ffc597b3ca9499470bda6ecc18ff66bcfad2
4
+ data.tar.gz: acb3307c3d9fcda2903ae19f026f1b4767950562c1507cb02afc2e433a4094c0
5
5
  SHA512:
6
- metadata.gz: 6a775fe77ed8459c6f223606da43d060ae618eb5c1c599d656f4b7774d42cba9169617681577dbb23d7c0e7e124d3da53a4f5e153a3dc720acf0fa09f550ca1f
7
- data.tar.gz: c095b66a7155e2ca6b8ae8d00fc183ecf8e45071c258a00d1a109792d1cbf7300f38c5fe5f9d7b285d393f36057867ff4be182cdfb747c5fc44e1e1f5009b9cb
6
+ metadata.gz: 89d7b3eb46518f18b718cef888205a1a62f03a079bd1e8600aae8217b99f882aa18a7fcc3a7787ab727c05a0a8ddc15690bcfc81ab8e19b2a7cb01c1f5034ac8
7
+ data.tar.gz: bbbd9bc7fb32e52edfe4451c98c534330d577380074ca3095f00d7666f72e8879fc9efaffd0a141ee86daf006db78cce878e304af21eae50c555a5075da4d0a4
@@ -72,3 +72,6 @@
72
72
  # Local CA cert store path where hammer stores certificates fetched from the server.
73
73
  # Certs from the local storage are used only when neither :ssl_ca_file: nor :ssl_ca_path: is cofigured.
74
74
  #:local_ca_store_path: '~/.hammer/certs'
75
+
76
+ # Allows setting the SSL version to use when making API calls
77
+ #:ssl_version: 'TLSv1_2'
@@ -33,10 +33,15 @@ HammerCLIForeman::Host::InfoCommand.extend_output_definition do |definition|
33
33
  end
34
34
  # Returns output definition of the command or field specified with path.
35
35
  # Where:
36
- # path = Array of :key or/and :id or/and 'label']
36
+ # path is an Array of field's :key or/and :id or/and 'label'
37
37
  definition.at(path)
38
38
  # Returns field from current output definition.
39
39
  definition.find_field(:id)
40
+ # Finds and adds fields to a set (creates a new one if the set doesn't exist).
41
+ # Where:
42
+ # sets is an Array of String with set names
43
+ # fields is an Array of field's :key or/and :id or/and 'label'
44
+ definition.update_field_sets(sets, fields)
40
45
  # Deletes all fields from current output definition.
41
46
  definition.clear
42
47
  end
@@ -55,6 +60,12 @@ HammerCLIForeman::Host::InfoCommand.extend_output_definition do |definition|
55
60
  definition.at(:path)
56
61
  .find_field(:id).label = _('New label')
57
62
  end
63
+ # Update fields in a field set
64
+ # Adds fields with :field_id and 'My field' to 'SET' set
65
+ HammerCLIForeman::Host::InfoCommand.extend_output_definition do |definition|
66
+ definition.at(:path)
67
+ .update_field_sets('SET', [:field_id, 'My field'])
68
+ end
58
69
  # Expand a field with new definition
59
70
  HammerCLIForeman::Host::InfoCommand.extend_output_definition do |definition|
60
71
  definition.at([_('some'), :path])
@@ -164,6 +164,14 @@ In cases when you want to deprecate just one of more possible switches use the e
164
164
  :deprecated => { '--name' => _('Use --alias instead') }
165
165
  ```
166
166
 
167
+ #### Predefined options
168
+ Also Hammer offers predefined options now. Those are just options, but with
169
+ predefined functionality. To define them in your command use
170
+ `use_option :option_name` method.
171
+
172
+ Here is the list of predefined options:
173
+ * `:fields` Expects a list with fields to show in output, see [example](creating_commands.md#printing-hash-records).
174
+
167
175
 
168
176
  ### Option builders
169
177
  Hammer commands offer option builders that can be used for automatic option generation.
@@ -523,8 +531,12 @@ Imagine there's an API of some service that returns list of users:
523
531
  ```
524
532
 
525
533
  We can create an output definition that selects and formats some of the fields:
534
+
535
+ _NOTE_: Every field can be arranged in so-called field sets. All the fields by default go to `'DEFAULT'` and `'ALL'` sets. Fields which are in the `'DEFAULT'` set will be printed by default. To see printed other field sets, use predefined option `--fields NAME`, where `NAME` is a field set name in ALLCAPS.
526
536
  ```ruby
527
537
  class Command < HammerCLI::AbstractCommand
538
+ # To be able to select fields which should be printed
539
+ use_option :fields
528
540
 
529
541
  output do
530
542
  # Simple field with a label. The first parameter is the key in the printed hash.
@@ -536,7 +548,7 @@ class Command < HammerCLI::AbstractCommand
536
548
  field :roles, 'System Roles', Fields::List
537
549
 
538
550
  # Label is used for grouping fields.
539
- label 'Contacts' do
551
+ label 'Contacts', sets: ['ADDITIONAL', 'ALL'] do
540
552
  field :email, 'Email'
541
553
  field :phone, 'Phone No.'
542
554
  end
@@ -565,18 +577,38 @@ Using the base adapter the output will look like:
565
577
  ID: 1
566
578
  System Roles: Admin, Editor
567
579
  Name: Tom Sawyer
580
+ Created At: 2012/12/18 15:24:42
581
+
582
+ ID: 2
583
+ System Roles: Admin
584
+ Name: Huckleberry Finn
585
+ Created At: 2012/12/18 15:25:00
586
+ ```
587
+
588
+ Using the base adapter with `--fields ALL` or `--fields DEFAULT,ADDITIONAL` the output will look like:
589
+ ```
590
+ ID: 1
591
+ System Roles: Admin, Editor
592
+ Name: Tom Sawyer
593
+ Created At: 2012/12/18 15:24:42
568
594
  Contacts:
569
595
  Email: tom@email.com
570
596
  Phone No.: 123456111
571
- Created At: 2012/12/18 15:24:42
572
597
 
573
598
  ID: 2
574
599
  System Roles: Admin
575
600
  Name: Huckleberry Finn
601
+ Created At: 2012/12/18 15:25:00
576
602
  Contacts:
577
603
  Email: huckleberry@email.com
578
604
  Phone No.: 123456222
579
- Created At: 2012/12/18 15:25:00
605
+ ```
606
+
607
+ _NOTE_: `--fields` as well lets you to print desired fields only. E.g. to see the users' emails without any additional information use `--fields contacts/email`:
608
+ ```
609
+ Email: tom@email.com
610
+
611
+ Email: huckleberry@email.com
580
612
  ```
581
613
 
582
614
  You can optionally use the output definition from another command as a base and extend it with
@@ -1,6 +1,11 @@
1
1
  Release notes
2
2
  =============
3
- ### 0.17.1 (2019-05-02)
3
+ ### 0.18.0 (2019-08-01)
4
+ * Unsure minimal label length ([PR #310](https://github.com/theforeman/hammer-cli/pull/310)) ([#26960](http://projects.theforeman.org/issues/26960))
5
+ * The --fields option has set help ([PR #308](https://github.com/theforeman/hammer-cli/pull/308)) ([#26960](http://projects.theforeman.org/issues/26960))
6
+ * Filter fields properly ([#26961](http://projects.theforeman.org/issues/26961))
7
+ * Possibility to limit fields that are displayed ([PR #276](https://github.com/theforeman/hammer-cli/pull/276)) ([#19135](http://projects.theforeman.org/issues/19135))
8
+ * Allow setting the SSL version to use for API calls
4
9
  * Make sure list opts return a list ([#26703](http://projects.theforeman.org/issues/26703))
5
10
 
6
11
  ### 0.17.0 (2019-04-24)
@@ -9,13 +9,13 @@ require 'hammer_cli/options/validators/dsl_block_validator'
9
9
  require 'hammer_cli/clamp'
10
10
  require 'hammer_cli/subcommand'
11
11
  require 'hammer_cli/options/matcher'
12
+ require 'hammer_cli/options/predefined'
12
13
  require 'hammer_cli/help/builder'
13
14
  require 'hammer_cli/help/text_builder'
14
15
  require 'hammer_cli/command_extensions'
15
16
  require 'logging'
16
17
 
17
18
  module HammerCLI
18
-
19
19
  class AbstractCommand < Clamp::Command
20
20
  include HammerCLI::Subcommand
21
21
 
@@ -57,6 +57,12 @@ module HammerCLI
57
57
  end
58
58
  end
59
59
  end
60
+
61
+ def add_sets_help(help)
62
+ sets_details = HammerCLI::Help::Section.new(_('Predefined field sets'), nil, id: :s_sets_details, richtext: true)
63
+ sets_details.definition << HammerCLI::Help::Text.new(output_definition.sets_table)
64
+ help.definition.unshift(sets_details)
65
+ end
60
66
  end
61
67
 
62
68
  def adapter
@@ -118,9 +124,10 @@ module HammerCLI
118
124
 
119
125
  def self.help(invocation_path, builder = HammerCLI::Help::Builder.new)
120
126
  super(invocation_path, builder)
121
-
127
+ help_extension = HammerCLI::Help::TextBuilder.new(builder.richtext)
128
+ fields_switch = HammerCLI::Options::Predefined::OPTIONS[:fields].first[0]
129
+ add_sets_help(help_extension) if find_option(fields_switch)
122
130
  unless help_extension_blocks.empty?
123
- help_extension = HammerCLI::Help::TextBuilder.new(builder.richtext)
124
131
  help_extension_blocks.each do |extension_block|
125
132
  begin
126
133
  extension_block.call(help_extension)
@@ -129,8 +136,8 @@ module HammerCLI
129
136
  handler.handle_exception(e)
130
137
  end
131
138
  end
132
- builder.add_text(help_extension.string)
133
139
  end
140
+ builder.add_text(help_extension.string)
134
141
  builder.string
135
142
  end
136
143
 
@@ -161,13 +168,11 @@ module HammerCLI
161
168
  self.class.output_definition
162
169
  end
163
170
 
164
-
165
171
  def self.output_definition
166
172
  @output_definition = @output_definition || inherited_output_definition || HammerCLI::Output::Definition.new
167
173
  @output_definition
168
174
  end
169
175
 
170
-
171
176
  def interactive?
172
177
  HammerCLI.interactive?
173
178
  end
@@ -197,6 +202,7 @@ module HammerCLI
197
202
  raise ArgumentError, _('Command extensions should be inherited from %s.') % HammerCLI::CommandExtensions
198
203
  end
199
204
  extension.delegatee(self)
205
+ extension.extend_predefined_options(self)
200
206
  extension.extend_options(self)
201
207
  extension.extend_output(self)
202
208
  extension.extend_help(self)
@@ -205,6 +211,12 @@ module HammerCLI
205
211
  end
206
212
  end
207
213
 
214
+ def self.use_option(*names)
215
+ names.each do |name|
216
+ HammerCLI::Options::Predefined.use(name, self)
217
+ end
218
+ end
219
+
208
220
  protected
209
221
 
210
222
  def self.find_options(switch_filter, other_filters={})
@@ -316,7 +328,6 @@ module HammerCLI
316
328
  @option_collector ||= HammerCLI::Options::OptionCollector.new(self.class.recognised_options, add_validators(option_sources))
317
329
  end
318
330
 
319
-
320
331
  def option_sources
321
332
  sources = HammerCLI::Options::ProcessorList.new(name: 'DefaultInputs')
322
333
  sources << HammerCLI::Options::Sources::CommandLine.new(self)
@@ -348,6 +359,5 @@ module HammerCLI
348
359
  end
349
360
  od
350
361
  end
351
-
352
362
  end
353
363
  end
@@ -7,7 +7,7 @@ module HammerCLI::Apipie
7
7
  end
8
8
 
9
9
  def resource
10
- self.class.resource || parent_command_resource
10
+ self.class.resource || parent_command_resource || nil
11
11
  end
12
12
 
13
13
  def action
@@ -15,7 +15,7 @@ module HammerCLI
15
15
  ALLOWED_EXTENSIONS = %i[
16
16
  option command_options before_print data output help request
17
17
  request_headers headers request_options options request_params params
18
- option_sources
18
+ option_sources predefined_options use_option
19
19
  ].freeze
20
20
 
21
21
  def initialize(options = {})
@@ -54,6 +54,10 @@ module HammerCLI
54
54
  opts: opts, block: block }
55
55
  end
56
56
 
57
+ def self.use_option(*names)
58
+ @predefined_option_names = names
59
+ end
60
+
57
61
  def self.before_print(&block)
58
62
  @before_print_block = block
59
63
  end
@@ -91,6 +95,13 @@ module HammerCLI
91
95
  self.class.extend_options(command_class)
92
96
  end
93
97
 
98
+ def extend_predefined_options(command_class)
99
+ allowed = @only & %i[predefined_options use_option]
100
+ return if allowed.empty? || (allowed & @except).any?
101
+
102
+ self.class.extend_predefined_options(command_class)
103
+ end
104
+
94
105
  def extend_before_print(data)
95
106
  allowed = @only & %i[before_print data]
96
107
  return if allowed.empty? || (allowed & @except).any?
@@ -170,6 +181,11 @@ module HammerCLI
170
181
  end
171
182
  end
172
183
 
184
+ def self.extend_predefined_options(command_class)
185
+ command_class.send(:use_option, *@predefined_option_names)
186
+ logger.debug("Added predefined options for #{command_class}: #{@predefined_option_names}")
187
+ end
188
+
173
189
  def self.extend_before_print(data)
174
190
  return if @before_print_block.nil?
175
191
 
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HammerCLI
4
+ module Options
5
+ # Contains predefined by HammerCLI options for commands
6
+ module Predefined
7
+ OPTIONS = {
8
+ fields: [['--fields'], 'FIELDS',
9
+ _('Show specified fileds or predefined filed sets only. (See below)'),
10
+ format: HammerCLI::Options::Normalizers::List.new,
11
+ context_target: :fields]
12
+ }.freeze
13
+
14
+ def self.use(option_name, command_class)
15
+ unless OPTIONS.key?(option_name)
16
+ raise ArgumentError, _('There is no such predefined option %s.') % option_name
17
+ end
18
+ command_class.send(:option, *OPTIONS[option_name])
19
+ end
20
+ end
21
+ end
22
+ end
@@ -6,11 +6,12 @@ module HammerCLI::Output::Adapter
6
6
  []
7
7
  end
8
8
 
9
- def initialize(context={}, formatters={})
9
+ def initialize(context={}, formatters={}, filters = {})
10
10
  context[:verbosity] ||= HammerCLI::V_VERBOSE
11
11
  @context = context
12
12
  @formatters = HammerCLI::Output::Formatters::FormatterLibrary.new(filter_formatters(formatters))
13
13
  @paginate_by_default = true
14
+ @filters = filters
14
15
  end
15
16
 
16
17
  def paginate_by_default?
@@ -41,10 +42,14 @@ module HammerCLI::Output::Adapter
41
42
  raise NotImplementedError
42
43
  end
43
44
 
45
+ def reset_context
46
+ @context.delete(:fields)
47
+ end
48
+
44
49
  protected
45
50
 
46
- def field_filter
47
- HammerCLI::Output::FieldFilter.new
51
+ def filter_fields(fields)
52
+ HammerCLI::Output::FieldFilter.new(fields, field_filters)
48
53
  end
49
54
 
50
55
  def self.data_for_field(field, record)
@@ -71,17 +76,25 @@ module HammerCLI::Output::Adapter
71
76
  $stdout
72
77
  end
73
78
 
74
- def displayable_fields(fields, record, compact_only: false)
75
- fields.select do |field|
76
- field_data = data_for_field(field, record)
77
- if compact_only && !field_data.is_a?(HammerCLI::Output::DataMissing)
78
- true
79
- else
80
- field.display?(field_data)
81
- end
79
+ def field_filters
80
+ {
81
+ classes_filter: classes_filter,
82
+ sets_filter: sets_filter
83
+ }.merge(@filters) do |_, old_filter, new_filter|
84
+ old_filter + new_filter
82
85
  end
83
86
  end
84
87
 
88
+ def classes_filter
89
+ return [] if @context[:show_ids]
90
+
91
+ [Fields::Id]
92
+ end
93
+
94
+ def sets_filter
95
+ @context[:fields] || ['DEFAULT']
96
+ end
97
+
85
98
  private
86
99
 
87
100
  def filter_formatters(formatters_map)
@@ -94,6 +107,5 @@ module HammerCLI::Output::Adapter
94
107
  map
95
108
  end
96
109
  end
97
-
98
110
  end
99
111
  end
@@ -21,18 +21,12 @@ module HammerCLI::Output::Adapter
21
21
 
22
22
  protected
23
23
 
24
- def field_filter
25
- filtered = []
26
- filtered << Fields::Id unless @context[:show_ids]
27
- HammerCLI::Output::FieldFilter.new(filtered)
28
- end
29
-
30
24
  def render_fields(fields, data)
31
- output = ""
32
-
33
- fields = field_filter.filter(fields)
34
- fields = displayable_fields(fields, data)
35
-
25
+ output = ''
26
+ fields = filter_fields(fields).filter_by_classes
27
+ .filter_by_sets
28
+ .filter_by_data(data)
29
+ .filtered_fields
36
30
  label_width = label_width(fields)
37
31
 
38
32
  fields.collect do |field|
@@ -126,7 +126,7 @@ module HammerCLI::Output::Adapter
126
126
  end
127
127
  end
128
128
 
129
- def initialize(context={}, formatters={})
129
+ def initialize(context = {}, formatters = {}, filters = {})
130
130
  super
131
131
  @paginate_by_default = false
132
132
  end
@@ -148,7 +148,11 @@ module HammerCLI::Output::Adapter
148
148
  end
149
149
 
150
150
  def print_collection(fields, collection)
151
- fields = displayable_fields(fields, collection.first, compact_only: true)
151
+ fields = filter_fields(fields).filter_by_classes
152
+ .filter_by_sets
153
+ .filter_by_data(collection.first,
154
+ compact_only: true)
155
+ .filtered_fields
152
156
  rows = row_data(fields, collection)
153
157
  # get headers using columns heuristic
154
158
  headers = rows.map{ |r| Cell.headers(r, @context) }.max_by{ |headers| headers.size }
@@ -198,7 +202,7 @@ module HammerCLI::Output::Adapter
198
202
  end
199
203
 
200
204
  def default_headers(fields)
201
- fields.select{ |f| !(f.class <= Fields::Id) || @context[:show_ids] }.map { |f| f.label }
205
+ fields.map(&:label)
202
206
  end
203
207
 
204
208
  end
@@ -21,9 +21,11 @@ module HammerCLI::Output::Adapter
21
21
  end
22
22
 
23
23
  def print_collection(all_fields, collection)
24
- fields = field_filter.filter(all_fields)
25
- fields = displayable_fields(fields, collection.first, compact_only: true)
26
-
24
+ fields = filter_fields(all_fields).filter_by_classes
25
+ .filter_by_sets
26
+ .filter_by_data(collection.first,
27
+ compact_only: true)
28
+ .filtered_fields
27
29
  formatted_collection = format_values(fields, collection)
28
30
  # calculate hash of column widths (label -> width)
29
31
  widths = calculate_widths(fields, formatted_collection)
@@ -63,6 +65,10 @@ module HammerCLI::Output::Adapter
63
65
 
64
66
  protected
65
67
 
68
+ def classes_filter
69
+ super << Fields::ContainerField
70
+ end
71
+
66
72
  def normalize_column(width, value)
67
73
  value = value.to_s
68
74
  padding = width - HammerCLI::Output::Utils.real_length(value)
@@ -103,12 +109,6 @@ module HammerCLI::Output::Adapter
103
109
  width
104
110
  end
105
111
 
106
- def field_filter
107
- filtered = [Fields::ContainerField]
108
- filtered << Fields::Id unless @context[:show_ids]
109
- HammerCLI::Output::FieldFilter.new(filtered)
110
- end
111
-
112
112
  private
113
113
 
114
114
  def max_width_for(field)
@@ -1,7 +1,6 @@
1
1
  module HammerCLI::Output::Adapter
2
2
  class TreeStructure < Abstract
3
-
4
- def initialize(context={}, formatters={})
3
+ def initialize(context = {}, formatters = {}, filters = {})
5
4
  super
6
5
  @paginate_by_default = false
7
6
  end
@@ -34,15 +33,11 @@ module HammerCLI::Output::Adapter
34
33
 
35
34
  protected
36
35
 
37
- def field_filter
38
- filtered = []
39
- filtered << Fields::Id unless @context[:show_ids]
40
- HammerCLI::Output::FieldFilter.new(filtered)
41
- end
42
-
43
36
  def render_fields(fields, data)
44
- fields = field_filter.filter(fields)
45
- fields = displayable_fields(fields, data)
37
+ fields = filter_fields(fields).filter_by_classes
38
+ .filter_by_sets
39
+ .filter_by_data(data)
40
+ .filtered_fields
46
41
  fields.reduce({}) do |hash, field|
47
42
  field_data = data_for_field(field, data)
48
43
  next unless field.display?(field_data)
@@ -19,6 +19,14 @@ module HammerCLI::Output
19
19
  @fields[field_index(field_id)]
20
20
  end
21
21
 
22
+ def update_field_sets(set_names, field_ids)
23
+ set_names = [set_names] unless set_names.is_a?(Array)
24
+ field_ids = [field_ids] unless field_ids.is_a?(Array)
25
+ field_ids.each do |field_id|
26
+ find_field(field_id).sets = find_field(field_id).sets.concat(set_names).uniq
27
+ end
28
+ end
29
+
22
30
  def insert(mode, field_id, fields = nil, &block)
23
31
  definition = self.class.new
24
32
  definition.append(fields, &block)
@@ -46,8 +54,84 @@ module HammerCLI::Output
46
54
  @fields.empty?
47
55
  end
48
56
 
57
+ def field_sets
58
+ nested_fields_sets(@fields).uniq.sort
59
+ end
60
+
61
+ def sets_table
62
+ fields_col_size = max_label_length || _('Fields').size
63
+ fields_col = normalize_column(fields_col_size, _('Fields'), centralize: true)
64
+ fields_col += ' ' unless (fields_col_size - fields_col.size).zero?
65
+ header_bits = [fields_col]
66
+ hline_bits = ['-' * fields_col_size]
67
+ field_sets.map do |set|
68
+ header_bits << normalize_column(set.size, set)
69
+ hline_bits << '-' * set.size
70
+ end
71
+ rows_bits = fields_row(@fields, field_sets, fields_col_size)
72
+ line = "+-#{hline_bits.join('-+-')}-+\n"
73
+ table = line
74
+ table += "| #{header_bits.join(' | ')} |\n"
75
+ table += line
76
+ table += "#{rows_bits.join("\n")}\n"
77
+ table += line
78
+ table
79
+ end
80
+
49
81
  private
50
82
 
83
+ def max_label_length
84
+ field_labels(@fields, full_labels: true).map(&:size).max
85
+ end
86
+
87
+ def normalize_column(width, col, centralize: false)
88
+ padding = width - HammerCLI::Output::Utils.real_length(col)
89
+ if padding >= 0
90
+ if centralize
91
+ padding /= 2
92
+ col.prepend(' ' * padding)
93
+ end
94
+ col += (' ' * padding)
95
+ else
96
+ col, real_len = HammerCLI::Output::Utils.real_truncate(col, width - 3)
97
+ col += '...'
98
+ col += ' ' if real_len < (width - 3)
99
+ end
100
+ col
101
+ end
102
+
103
+ def fields_row(fields, sets, fields_col_size)
104
+ fields.each_with_object([]) do |field, rows|
105
+ next rows << fields_row(field.fields, sets, fields_col_size) if field.respond_to?(:fields)
106
+
107
+ row = [normalize_column(fields_col_size, field.full_label)]
108
+ sets.each do |set|
109
+ mark = field.sets.include?(set) ? 'x' : ' '
110
+ column = normalize_column(set.size, mark, centralize: true)
111
+ column += ' ' unless (set.size - column.size).zero?
112
+ row << column
113
+ end
114
+ rows << "| #{row.join(' | ')} |"
115
+ end
116
+ end
117
+
118
+ def field_labels(fields, full_labels: false)
119
+ fields.each_with_object([]) do |field, labels|
120
+ label = full_labels ? field.full_label : field.label
121
+ next labels << label unless field.respond_to?(:fields)
122
+
123
+ labels.concat(field_labels(field.fields, full_labels: full_labels))
124
+ end
125
+ end
126
+
127
+ def nested_fields_sets(fields)
128
+ fields.map do |field|
129
+ next field.sets unless field.respond_to?(:fields)
130
+
131
+ nested_fields_sets(field.fields)
132
+ end.flatten
133
+ end
134
+
51
135
  def field_index(field_id)
52
136
  index = @fields.find_index do |f|
53
137
  f.match_id?(field_id)
@@ -1,21 +1,84 @@
1
1
  module HammerCLI::Output
2
-
3
2
  class FieldFilter
3
+ attr_reader :fields, :filtered_fields
4
+ attr_accessor :classes_filter, :sets_filter
5
+
6
+ def initialize(fields = [], filters = {})
7
+ self.fields = fields
8
+ @classes_filter = filters[:classes_filter] || []
9
+ @sets_filter = filters[:sets_filter] || []
10
+ end
4
11
 
5
- def initialize(field_classes=[])
6
- @field_classes = field_classes
12
+ def fields=(fields)
13
+ @fields = fields || []
14
+ @filtered_fields = @fields.dup
7
15
  end
8
16
 
9
- def filter(fields)
10
- fields = fields.clone
11
- @field_classes.each do |cls|
12
- fields.reject! do |f|
17
+ def filter_by_classes(classes = nil)
18
+ classes ||= @classes_filter
19
+ classes.each do |cls|
20
+ @filtered_fields.reject! do |f|
13
21
  f.is_a? cls
14
22
  end
15
23
  end
16
- fields
24
+ self
17
25
  end
18
26
 
19
- end
27
+ def filter_by_sets(sets = nil)
28
+ sets ||= @sets_filter
29
+ return self if sets.empty?
30
+
31
+ set_names, labels = resolve_set_names(sets)
32
+ deep_filter(@filtered_fields, set_names, labels)
33
+ self
34
+ end
35
+
36
+ def filter_by_data(data, compact_only: false)
37
+ @filtered_fields = displayable_fields(@filtered_fields,
38
+ data,
39
+ compact_only: compact_only)
40
+ self
41
+ end
42
+
43
+ private
44
+
45
+ def deep_filter(fields, set_names, labels)
46
+ fields.select! do |f|
47
+ allowed = include_by_label?(labels, f.full_label.downcase)
48
+ allowed ||= (f.sets & set_names).any?
49
+ deep_filter(f.fields, set_names, labels) if f.respond_to?(:fields)
50
+ allowed
51
+ end
52
+ end
20
53
 
54
+ def displayable_fields(fields, record, compact_only: false)
55
+ fields.select do |field|
56
+ field_data = HammerCLI::Output::Adapter::Abstract.data_for_field(
57
+ field, record
58
+ )
59
+ if compact_only && !field_data.is_a?(HammerCLI::Output::DataMissing)
60
+ true
61
+ else
62
+ field.display?(field_data)
63
+ end
64
+ end
65
+ end
66
+
67
+ def include_by_label?(labels, label)
68
+ labels.any? do |l|
69
+ l.start_with?("#{label}/") || label.match(%r{^#{l.gsub(/\*/, '.*')}(|\/.*)$})
70
+ end
71
+ end
72
+
73
+ def resolve_set_names(sets)
74
+ set_names = []
75
+ labels = []
76
+ sets.each do |name|
77
+ next set_names << name if name.upcase == name
78
+
79
+ labels << name.downcase
80
+ end
81
+ [set_names, labels]
82
+ end
83
+ end
21
84
  end
@@ -1,16 +1,17 @@
1
1
  require 'hammer_cli/output/dsl'
2
2
 
3
3
  module Fields
4
-
5
4
  class Field
6
5
  attr_reader :path
7
- attr_accessor :label
6
+ attr_writer :sets
7
+ attr_accessor :label, :parent
8
8
 
9
9
  def initialize(options={})
10
10
  @hide_blank = options[:hide_blank].nil? ? false : options[:hide_blank]
11
11
  @hide_missing = options[:hide_missing].nil? ? true : options[:hide_missing]
12
12
  @path = options[:path] || []
13
13
  @label = options[:label]
14
+ @sets = options[:sets]
14
15
  @options = options
15
16
  end
16
17
 
@@ -30,6 +31,15 @@ module Fields
30
31
  @hide_missing
31
32
  end
32
33
 
34
+ def full_label
35
+ return @label.to_s if @parent.nil?
36
+ "#{@parent.full_label}/#{@label}"
37
+ end
38
+
39
+ def sets
40
+ @sets || inherited_sets || default_sets
41
+ end
42
+
33
43
  def display?(value)
34
44
  if value.is_a?(HammerCLI::Output::DataMissing)
35
45
  !hide_missing?
@@ -44,6 +54,16 @@ module Fields
44
54
  @options
45
55
  end
46
56
 
57
+ protected
58
+
59
+ def inherited_sets
60
+ return nil if @parent.nil?
61
+ @parent.sets
62
+ end
63
+
64
+ def default_sets
65
+ %w[DEFAULT ALL]
66
+ end
47
67
  end
48
68
 
49
69
 
@@ -53,7 +73,7 @@ module Fields
53
73
  super(options)
54
74
  dsl = HammerCLI::Output::Dsl.new
55
75
  dsl.build &block if block_given?
56
-
76
+ dsl.fields.each { |f| f.parent = self }
57
77
  self.output_definition.append dsl.fields
58
78
  end
59
79
 
@@ -25,6 +25,7 @@ module HammerCLI::Output
25
25
 
26
26
  def print_record(definition, record)
27
27
  adapter.print_record(definition.fields, record) if appropriate_verbosity?(:record)
28
+ adapter.reset_context
28
29
  end
29
30
 
30
31
  def print_collection(definition, collection)
@@ -32,6 +33,7 @@ module HammerCLI::Output
32
33
  collection = HammerCLI::Output::RecordCollection.new([collection].flatten(1))
33
34
  end
34
35
  adapter.print_collection(definition.fields, collection) if appropriate_verbosity?(:collection)
36
+ adapter.reset_context
35
37
  end
36
38
 
37
39
  def adapter
@@ -14,7 +14,7 @@ module HammerCLI
14
14
 
15
15
  def get_options(uri = nil)
16
16
  ssl_options = {}
17
- for sslopt in [:ssl_ca_file, :ssl_ca_path, :verify_ssl] do
17
+ for sslopt in [:ssl_ca_file, :ssl_ca_path, :verify_ssl, :ssl_version] do
18
18
  ssloptval = read_ssl_option(sslopt)
19
19
  ssl_options[sslopt] = ssloptval unless ssloptval.nil?
20
20
  end
@@ -1,5 +1,5 @@
1
1
  module HammerCLI
2
2
  def self.version
3
- @version ||= Gem::Version.new '0.17.1'
3
+ @version ||= Gem::Version.new '0.18.0'
4
4
  end
5
5
  end
@@ -270,6 +270,7 @@ describe HammerCLI::AbstractCommand do
270
270
  option "--test", "TEST", "Test option"
271
271
  option "--test-format", "TEST_FORMAT", "Test option with a formatter",
272
272
  :format => HammerCLI::Options::Normalizers::List.new
273
+ use_option :fields
273
274
  end
274
275
 
275
276
  it "should create instances of hammer options" do
@@ -282,6 +283,10 @@ describe HammerCLI::AbstractCommand do
282
283
  opt.value_formatter.kind_of?(HammerCLI::Options::Normalizers::List).must_equal true
283
284
  end
284
285
 
286
+ it 'should allow using of predefined options' do
287
+ opt = TestOptionCmd.find_option('--fields')
288
+ opt.is_a?(HammerCLI::Options::OptionDefinition).must_equal true
289
+ end
285
290
  end
286
291
 
287
292
  describe "#options" do
@@ -257,4 +257,24 @@ describe HammerCLI::Output::Definition do
257
257
  definition.at(path).must_equal label_field.output_definition
258
258
  end
259
259
  end
260
+
261
+ describe 'sets_table' do
262
+ it 'prints a table with fields and sets ' do
263
+ cont_field = Fields::ContainerField.new(id: :id1, label: 'cf', sets: ['SET']) do
264
+ field :a, 'abc', Fields::Field
265
+ field :b, 'bca', Fields::Field
266
+ end
267
+ definition.fields += [new_field, cont_field]
268
+
269
+ sets_table = "+----------+-----+---------+-----+
270
+ | Fields | ALL | DEFAULT | SET |
271
+ +----------+-----+---------+-----+
272
+ | newfield | x | x | |
273
+ | cf/abc | | | x |
274
+ | cf/bca | | | x |
275
+ +----------+-----+---------+-----+\n"
276
+
277
+ definition.sets_table.must_equal sets_table
278
+ end
279
+ end
260
280
  end
@@ -1,27 +1,72 @@
1
1
  require File.join(File.dirname(__FILE__), '../test_helper')
2
2
 
3
3
  describe HammerCLI::Output::FieldFilter do
4
-
5
- let(:fields) { [
6
- Fields::Field.new(:label => "field"),
7
- Fields::Collection.new(:label => "collection"),
8
- Fields::Id.new(:label => "id")
9
- ] }
4
+ let(:fields) do
5
+ [
6
+ Fields::Field.new(:label => 'field', :hide_blank => true),
7
+ Fields::Collection.new(:label => 'collection'),
8
+ Fields::Id.new(:label => 'id', :sets => ['THIN'])
9
+ ]
10
+ end
11
+ let(:container_fields) do
12
+ fields + [
13
+ Fields::ContainerField.new(:label => 'container') do
14
+ field :first, 'first'
15
+ field :second, 'second', Fields::ContainerField do
16
+ field :nested, 'nested'
17
+ end
18
+ end
19
+ ]
20
+ end
10
21
  let(:field_labels) { fields.map(&:label).sort }
11
22
 
12
- it "lets all fields go by default" do
13
- f = HammerCLI::Output::FieldFilter.new
14
- f.filter(fields).map(&:label).sort.must_equal ["field", "collection", "id"].sort
23
+ it 'lets all fields go by default' do
24
+ f = HammerCLI::Output::FieldFilter.new(fields)
25
+ f.filtered_fields.map(&:label).sort.must_equal ['field', 'collection', 'id'].sort
26
+ end
27
+
28
+ it 'filters fields by class' do
29
+ f = HammerCLI::Output::FieldFilter.new(fields, classes_filter: [Fields::Id])
30
+ f.filter_by_classes.filtered_fields.map(&:label).sort.must_equal ['field', 'collection'].sort
31
+ end
32
+
33
+ it 'filters fields by superclass' do
34
+ f = HammerCLI::Output::FieldFilter.new(fields, classes_filter: [Fields::ContainerField])
35
+ f.filter_by_classes.filtered_fields.map(&:label).sort.must_equal ['field', 'id'].sort
36
+ end
37
+
38
+ it 'filters fields by sets' do
39
+ f = HammerCLI::Output::FieldFilter.new(fields, sets_filter: ['THIN'])
40
+ f.filter_by_sets.filtered_fields.map(&:label).must_equal ['id']
41
+ end
42
+
43
+ it 'filters fields by sets with labels' do
44
+ f = HammerCLI::Output::FieldFilter.new(fields, sets_filter: ['THIN', 'field'])
45
+ f.filter_by_sets.filtered_fields.map(&:label).sort.must_equal ['field', 'id'].sort
46
+ end
47
+
48
+ it 'filters by full labels' do
49
+ f = HammerCLI::Output::FieldFilter.new(container_fields, sets_filter: ['container/first'])
50
+ f.filter_by_sets.filtered_fields.first.fields.map(&:label).must_equal ['first']
51
+ end
52
+
53
+ it 'filters by superclass labels' do
54
+ f = HammerCLI::Output::FieldFilter.new(container_fields, sets_filter: ['container'])
55
+ f.filter_by_sets.filtered_fields.first.fields.map(&:label).must_equal ['first', 'second']
15
56
  end
16
57
 
17
- it "filters fields by class" do
18
- f = HammerCLI::Output::FieldFilter.new([Fields::Id])
19
- f.filter(fields).map(&:label).sort.must_equal ["field", "collection"].sort
58
+ it 'filters by labels with wildcards' do
59
+ f = HammerCLI::Output::FieldFilter.new(container_fields, sets_filter: ['container/f*'])
60
+ f.filter_by_sets.filtered_fields.first.fields.map(&:label).must_equal ['first']
20
61
  end
21
62
 
22
- it "filters fields by superclass" do
23
- f = HammerCLI::Output::FieldFilter.new([Fields::ContainerField])
24
- f.filter(fields).map(&:label).sort.must_equal ["field", "id"].sort
63
+ it 'allows chained filtering' do
64
+ f = HammerCLI::Output::FieldFilter.new(fields, sets_filter: ['THIN'], classes_filter: [Fields::Id])
65
+ f.filter_by_classes.filter_by_sets.filtered_fields.map(&:label).must_equal []
25
66
  end
26
67
 
68
+ it 'filters fields by data' do
69
+ f = HammerCLI::Output::FieldFilter.new(fields)
70
+ f.filter_by_data(nil).filtered_fields.map(&:label).sort.must_equal ['id', 'collection'].sort
71
+ end
27
72
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hammer_cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.1
4
+ version: 0.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Bačovský
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-05-02 00:00:00.000000000 Z
12
+ date: 2019-08-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: clamp
@@ -242,6 +242,7 @@ files:
242
242
  - lib/hammer_cli/options/option_collector.rb
243
243
  - lib/hammer_cli/options/option_definition.rb
244
244
  - lib/hammer_cli/options/option_processor.rb
245
+ - lib/hammer_cli/options/predefined.rb
245
246
  - lib/hammer_cli/options/processor_list.rb
246
247
  - lib/hammer_cli/options/sources/base.rb
247
248
  - lib/hammer_cli/options/sources/command_line.rb