hammer_cli 0.17.1 → 0.18.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 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