Skip to content

Commit

Permalink
Fixes #21634 - Allows modification of output fields
Browse files Browse the repository at this point in the history
  • Loading branch information
ofedoren committed Jun 20, 2018
1 parent c0b2ca6 commit 76b9076
Show file tree
Hide file tree
Showing 6 changed files with 331 additions and 4 deletions.
54 changes: 54 additions & 0 deletions doc/commands_modification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
Modify an existing command
-------------------------

### Modification of output fields

Each command (as well as each field) might have its own
output definition. You can modify the output definition of existing
command or a specific field of that command by following output
definition interface:

```ruby
# Inserts one or more fields.
# Where:
# :mode is one of [:before, :after, :replace]
# :field_id is field's id or key. Field's label can be used if field does not
# have id
# :fields is one or more existing fields
# :block is block of code with new fields
insert(:mode, :field_id, fields) do
label
field
collection
end
# Returns output definition of the command or field specified with path.
# Where:
# path = Array of :field_key or/and :field_id or/and 'field_label']
at(path)
# Returns field from current output definition.
find_field(:field_id)
# Deletes all fields from current output definition.
clear
```
#### Examples
```ruby
# Change field's label
HammerCLIForeman::Host::InfoCommand.output_definition
.at(:path)
.find_field(:id).label = 'New label'
# Expand a field with new definition
HammerCLIForeman::Host::InfoCommand.output_definition
.at(['some', :path])
.insert(:replace, :not_container_field_id, [Fields::Field.new(:first => :one)]) do
field nil, _('Label with new fields'), Fields::Label, field_id: :now_container_field_id do
field :new_field1, _('New field 1')
field :new_field2, _('New field 2')
end
end
# Simplified version
HammerCLIForeman::Host::InfoCommand.output_definition
.insert(:before, :field_id, Fields::Field.new(:first => :one))
# Remove field
HammerCLIForeman::Host::InfoCommand.output_definition
.insert(:replace, :field_id) do; end
```
1 change: 1 addition & 0 deletions doc/developer_docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ before creating hammer specific plugins.
Contents:
- [Writing a plugin](writing_a_plugin.md#writing-your-own-hammer-plugin)
- [Creating commands](creating_commands.md#create-your-first-command)
- [Commands modification](commands_modification.md#modify-an-existing-command)
- [Option builders](option_builders.md#option-builders)
- [Creating ApiPie commands](creating_apipie_commands.md#creating-commands-for-restful-api-with-apipie)
- [Development tips](development_tips.md#development-tips)
Expand Down
60 changes: 59 additions & 1 deletion lib/hammer_cli/output/definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,72 @@ def initialize
@fields = []
end

def build(dsl = Dsl.new, &block)
dsl.build(&block) if block_given?
append(dsl.fields)
end

def append(fields)
return @fields if fields.nil?
fields = [fields] unless fields.is_a? Array
@fields += fields
end

def find_field(field_id)
@fields[field_index(field_id)]
end

def insert(mode = :after, field_id = nil, fields = nil, &block)
index = field_index(field_id)
index += 1 if mode == :after
definition = self.class.new
definition.append(fields)
definition.build(&block)
insert_fields(index, definition.fields, with_remove: mode == :replace)
end

def at(path = [])
path = [path] unless path.is_a? Array
return self if path.empty?
field = expand_path(@fields, path.dup)
if field.nil? || !field.respond_to?(:output_definition)
raise ArgumentError, 'No output definition at path'
end
field.output_definition
end

def clear
@fields = []
end

def empty?
@fields.empty?
end

end
private

def field_index(field_id)
index = @fields.find_index do |f|
f.id == field_id
end
raise NameError, 'No such field' if index.nil?
index
end

def insert_fields(index, fields, with_remove: false)
@fields.delete_at(index) if with_remove
fields.each_with_index do |f, i|
@fields.insert(index + i, f)
end
end

def expand_path(fields, path)
id = path.shift
field = fields.drop_while do |f|
f.id != id
end.first
return field if path.empty? || !field.respond_to?(:output_definition)
expand_path(field.output_definition.fields, path)
end
end
end
2 changes: 2 additions & 0 deletions lib/hammer_cli/output/dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def build(&block)
def field(key, label, type=nil, options={}, &block)
options[:path] = current_path.clone
options[:path] << key if !key.nil?
options[:field_id] = options[:field_id] || key || options[:key]

options[:label] = label
type ||= Fields::Field
Expand All @@ -33,6 +34,7 @@ def custom_field(type, options={}, &block)
def label(label, options={}, &block)
options[:path] = current_path.clone
options[:label] = label
options[:field_id] ||= options[:key]
custom_field Fields::Label, options, &block
end

Expand Down
6 changes: 4 additions & 2 deletions lib/hammer_cli/output/fields.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ module Fields

class Field

attr_reader :label
attr_reader :path
attr_accessor :label
attr_accessor :path
attr_accessor :id

def initialize(options={})
@hide_blank = options[:hide_blank].nil? ? false : options[:hide_blank]
@path = options[:path] || []
@label = options[:label]
@id = options[:field_id] || label || options[:key]
@options = options
end

Expand Down
212 changes: 211 additions & 1 deletion test/unit/output/definition_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
let(:definition) { HammerCLI::Output::Definition.new }
let(:last_field) { definition.fields[-1] }
let(:field_count) { definition.fields.length }
let(:new_field) { Fields::Field.new(label: 'newfield', field_id: :new_id) }
let(:label_field) { Fields::Label.new(label: 'labelfield') }
let(:cont_field) { Fields::ContainerField.new(field_id: :id1) }


describe "empty?" do

Expand Down Expand Up @@ -36,5 +40,211 @@
definition.fields.must_equal another_def.fields
end

end
it 'clear should delete all fields' do
definition.fields << Fields::Field.new
definition.clear
definition.empty?.must_equal true
end

describe 'insert' do
let(:new_fields) { [new_field, new_field] }

describe 'before' do
it 'should not insert field if output definition is empty' do
definition.clear
assert_raises NameError do
definition.insert(:before, :id1, new_field)
end

field_count.must_equal 0
end

it 'should insert new specified field before the old one' do
definition.fields << Fields::Field.new(field_id: :id1, label: 'oldfield')
definition.insert(:before, :id1, new_field)

definition.fields.first.label.must_equal new_field.label
field_count.must_equal 2
end

it 'should insert before field with few new specified' do
definition.fields << Fields::Field.new(field_id: :id1, label: 'oldfield')
definition.insert(:before, :id1, new_fields)

definition.fields.first.label.must_equal new_field.label
field_count.must_equal 3
end

it 'should accept block with new fields' do
definition.fields << Fields::Field.new(field_id: :id1, label: 'oldfield')
definition.insert(:before, :id1) do
field nil, 'newfield'
field nil, 'newfield2'
end

definition.fields.first.label.must_equal new_field.label
field_count.must_equal 3
end

it 'should accept both block and new fields' do
definition.fields << Fields::Field.new(field_id: :id1, label: 'oldfield')
definition.insert(:before, :id1, new_fields) do
field nil, 'newfield3'
field nil, 'newfield4'
end

definition.fields.first.label.must_equal new_field.label
field_count.must_equal 5
end

it 'should work with labels' do
label_field.output_definition.fields << new_field
definition.fields << label_field
definition.insert(:before, label_field.label, new_fields)

definition.fields.first.label.must_equal new_field.label
field_count.must_equal 3
end
end

describe 'after' do
it 'should not insert field if output definition is empty' do
definition.clear
assert_raises NameError do
definition.insert(:after, :id1, new_field)
end

field_count.must_equal 0
end

it 'should insert new specified field after the old one' do
definition.fields << Fields::Field.new(field_id: :id1, label: 'oldfield')
definition.insert(:after, :id1, new_field)

definition.fields.first.label.must_equal 'oldfield'
field_count.must_equal 2
end

it 'should insert after field with few new specified' do
definition.fields << Fields::Field.new(field_id: :id1, label: 'oldfield')
definition.insert(:after, :id1, new_fields)

definition.fields.first.label.must_equal 'oldfield'
field_count.must_equal 3
end

it 'should accept block with new fields' do
definition.fields << Fields::Field.new(field_id: :id1, label: 'oldfield')
definition.insert(:after, :id1) do
field nil, 'newfield'
field nil, 'newfield2'
end

definition.fields.first.label.must_equal 'oldfield'
field_count.must_equal 3
end

it 'should accept both block and new fields' do
definition.fields << Fields::Field.new(field_id: :id1, label: 'oldfield')
definition.insert(:after, :id1, new_fields) do
field nil, 'newfield3'
field nil, 'newfield4'
end

definition.fields.first.label.must_equal 'oldfield'
field_count.must_equal 5
end

it 'should work with labels' do
label_field.output_definition.fields << new_field
definition.fields << label_field
definition.insert(:after, label_field.label, new_fields)

definition.fields.first.label.must_equal label_field.label
field_count.must_equal 3
end
end

describe 'replace' do
it 'should not replace field if output definition is empty' do
definition.clear
assert_raises NameError do
definition.insert(:replace, :id1, new_field)
end

field_count.must_equal 0
end

it 'should replace field with new specified' do
definition.fields << Fields::Field.new(field_id: :id1, label: 'oldfield')
definition.insert(:replace, :id1, new_field)

definition.fields.first.label.must_equal new_field.label
field_count.must_equal 1
end

it 'should replace field with few new specified' do
definition.fields << Fields::Field.new(field_id: :id1, label: 'oldfield')
definition.insert(:replace, :id1, new_fields)

definition.fields.first.label.must_equal new_field.label
field_count.must_equal 2
end

it 'should accept block with new fields' do
definition.fields << Fields::Field.new(field_id: :id1, label: 'oldfield')
definition.insert(:replace, :id1) do
field nil, 'newfield'
field nil, 'newfield2'
end

definition.fields.first.label.must_equal new_field.label
end

it 'should accept both block and new fields' do
definition.fields << Fields::Field.new(field_id: :id1, label: 'oldfield')
definition.insert(:replace, :id1, new_fields) do
field nil, 'newfield3'
field nil, 'newfield4'
end

field_count.must_equal 4
end

it 'should work with labels' do
label_field.output_definition.fields << new_field
definition.fields << label_field
definition.insert(:replace, label_field.label, new_fields)

field_count.must_equal 2
end
end
end

describe 'find_field' do
# -
end

describe 'at' do
it 'should return self if no specified path or empty' do
definition.at.must_equal definition
definition.at([]).must_equal definition
end

it 'should return output definition of specified field with path' do
cont_field.output_definition.fields << new_field
definition.fields << cont_field
path = [cont_field.id]

definition.at(path).must_equal cont_field.output_definition
end

it 'should work with labels' do
label_field.output_definition.fields << new_field
definition.fields << label_field
path = ['labelfield']

definition.at(path).must_equal label_field.output_definition
end
end
end

0 comments on commit 76b9076

Please sign in to comment.