Skip to content

Commit

Permalink
Merge c344dd9 into e05b1e3
Browse files Browse the repository at this point in the history
  • Loading branch information
imobachgs committed Feb 1, 2019
2 parents e05b1e3 + c344dd9 commit d346f9a
Show file tree
Hide file tree
Showing 23 changed files with 179 additions and 98 deletions.
2 changes: 1 addition & 1 deletion src/lib/y2configuration_management/salt/form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def self.from_file(path)
#
# f = Y2ConfigurationManagemenet.from_file("form.yml")
# f.find_element_by(name: "subnets") #=> <Collection @name="subnets">
# locator = FormElementLocator.from_string(".root.dhcpd")
# locator = FormElementLocator.from_string("root#dhcpd")
# f.find_element_by(locator: locator) #=> <Container @name="dhcpd">
# f.find_element_by(id: "hosts") #=> <Container @id="hosts">
#
Expand Down
2 changes: 1 addition & 1 deletion src/lib/y2configuration_management/salt/form_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ def add_or_edit_item(action, relative_locator)
item_locator = new_item_locator_for_action(action, relative_locator)
form_widget = item_form_for(item_locator)
state.open_form(item_locator, form_widget)
form_widget.value = find_or_create_item(item_locator)
result = show_popup(form_widget)
form_data.update(state.locator, result) unless result.nil?
state.close_form(rollback: result.nil?)
Expand All @@ -200,7 +201,6 @@ def item_form_for(locator)
element = form.find_element_by(locator: locator.unbounded)
form_widget = form_builder.build(element.prototype)
form_widget.title = element.name
form_widget.value = find_or_create_item(locator)
form_widget
end

Expand Down
88 changes: 64 additions & 24 deletions src/lib/y2configuration_management/salt/form_element_locator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,25 @@ module Salt
# Represent the locator to a form element
#
# The locator can be seen as a path to the form element. In a human readable form, the locator
# looks like: ".root.person.computers[1]" or ".root.hosts[router]".
# looks like: "root#person#computers[1]" or "root#hosts[router]".
#
# @example Building a locator from a string for an array based collection
# locator = FormElementLocator.from_string(".root.person.computers[1]")
# locator.to_s #=> ".root.person.computers[1]"
# locator = FormElementLocator.from_string("root#person#computers[1]")
# locator.to_s #=> "root#person#computers[1]"
# locator.parts #=> [:root, :person, :computers, 1]
#
# @example Building a locator from a string for a hash based collection
# locator = FormElementLocator.from_string(".root.hosts[router]")
# locator.to_s #=> ".root.hosts[router]"
# locator = FormElementLocator.from_string("root#hosts[router]")
# locator.to_s #=> "root#hosts[router]"
# locator.parts #=> [:root, :hosts, "router"]
#
# @example Building a locator from its parts
# locator = FormElementLocator.new(:root, :hosts, "router")
# locator.to_s #=> ".root.hosts[router]"
# locator.to_s #=> "root#hosts[router]"
#
# @example Extending a locator
# locator = FormElementLocator.new(:root, :hosts, "router")
# locator.join(:interfaces).to_s #=> "root#hosts[router]#interfaces"
class FormElementLocator
extend Forwardable

Expand All @@ -54,29 +58,41 @@ class << self
# @param string [String] String representing an element locator
# @return [FormElementLocator]
def from_string(string)
parts = string[1..-1].scan(/(?:\[.*?\]|[^\.\[])+/).each_with_object([]) do |part, all|
all.concat(from_part(part))
string.scan(TOKENS).reduce(nil) do |locator, part|
new_locator = from_part(part)
locator ? locator.join(from_part(part)) : new_locator
end
new(parts)
end

private

# @return [Regexp] Regular expression representing a locator part
INDEXED_PART = /\A([^\[]+)\[(.+)\]\z/
# @return [String] Locator segments separator
SEPARATOR = "#".freeze

# @return [Regexp] Regular expresion to extract locator segments
TOKENS = /(?:\[.*?\]|[^#{SEPARATOR}\[])+/

# @return [Regexp] Regular expression representing a indexed locator segment
INDEXED_SEGMENT = /([^\[]+)(?:\[(.+)\])?/

# @return []
SEGMENT = /(\.*)#{INDEXED_SEGMENT}/

# Parses a locator part
#
# @param string [String]
# @return [Array<Integer,String,Symbol>] Locator subparts
def from_part(string)
match = INDEXED_PART.match(string)
return [string.to_sym] unless match
path = match[1]
ids = match[2].split("][").map do |id|
match = SEGMENT.match(string)
return nil unless match
prefix, path, index = match[1..3]

ids = index.to_s.split("][").map do |id|
numeric_id?(id) ? id.to_i : id
end
[path.to_sym] + ids

parts = [path.to_sym] + ids
FormElementLocator.new(parts, upto: prefix.size)
end

# Determines whether the id is numeric or not
Expand All @@ -90,11 +106,15 @@ def numeric_id?(id)
# @return [Array<Integer,String,Symbol>] Locator parts
attr_reader :parts

# @return [Integer]
attr_reader :upto

# Constructor
#
# @param parts [Array<Integer,String,Symbol>] Locator parts
def initialize(parts)
def initialize(parts, upto: 0)
@parts = parts
@upto = upto
end

# Locator of the parent element
Expand All @@ -115,10 +135,12 @@ def rest
#
# @return [String] String representation
def to_s
parts.reduce("") do |memo, part|
part_as_string = part.is_a?(Integer) ? "[#{part}]" : ".#{part}"
as_string = parts.reduce("") do |memo, part|
part_as_string = part.is_a?(Symbol) ? "##{part}" : "[#{part}]"
memo << part_as_string
end
prefix = relative? ? "." * upto : ""
prefix + as_string[1..-1]
end

# Extends a locator
Expand All @@ -127,19 +149,18 @@ def to_s
# to join
# @return [Locator] Augmented locator
def join(*locators_or_parts)
new_parts = locators_or_parts.reduce([]) do |all, item|
item_parts = item.respond_to?(:parts) ? item.parts : [item]
all + item_parts
locators_or_parts.reduce(self) do |locator, item|
other = item.is_a?(FormElementLocator) ? item : FormElementLocator.new([item])
locator.join_with_locator(other)
end
self.class.new(parts + new_parts)
end

# Determines whether two locators are equivalent
#
# @param other [Locator] Locator to compare with
# @return [Boolean] true if both locators are equal; false otherwise
def ==(other)
parts == other.parts
upto == other.upto && parts == other.parts
end

# Removes references to specific collection elements
Expand All @@ -148,6 +169,25 @@ def ==(other)
def unbounded
self.class.new(parts.select { |i| i.is_a?(Symbol) })
end

# Determines whether a locator is relative or not
#
# @return [Boolean] true if its relative; false otherwise
def relative?
!upto.zero?
end

protected

# Extends a locator with another one
#
# @param other [FormElementLocator] Locator to join
# @return [Locator] Augmented locator
# @see join
def join_with_locator(other)
limit = -1 - other.upto
self.class.new(parts[0..limit] + other.parts, upto: upto)
end
end
end
end
14 changes: 7 additions & 7 deletions test/y2configuration_management/salt/form_builder_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@

describe "#build" do
context "when an input form element is given" do
let(:locator) { locator_from_string(".root.person.name") }
let(:locator) { locator_from_string("root#person#name") }

it "returns a form containing a text widget" do
form_widget = builder.build(element)
expect(form_widget.children).to be_all(Y2ConfigurationManagement::Widgets::Text)
expect(form_widget.children).to contain_exactly(
an_object_having_attributes(
"locator" => locator_from_string(".root.person.name")
"locator" => locator_from_string("root#person#name")
)
)
end
Expand All @@ -51,25 +51,25 @@
end

context "when a group form element is given" do
let(:locator) { locator_from_string(".root.person.address") }
let(:locator) { locator_from_string("root#person#address") }

it "returns a form containing group widgets" do
form_widget = builder.build(element)
expect(form_widget.children.map(&:relative_locator)).to contain_exactly(
locator_from_string(".street"),
locator_from_string(".country")
locator_from_string("#street"),
locator_from_string("#country")
)
end
end

context "when a collection is given" do
let(:locator) { locator_from_string(".root.person.computers") }
let(:locator) { locator_from_string("root#person#computers") }

it "returns a form containing a collection widget" do
form_widget = builder.build(element)
expect(form_widget.children).to contain_exactly(
an_object_having_attributes(
"relative_locator" => locator_from_string(".computers")
"relative_locator" => locator_from_string("#computers")
)
)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
subject(:state) { described_class.new(data) }
let(:form_widget) { instance_double(Y2ConfigurationManagement::Widgets::Form) }
let(:form_widget_1) { instance_double(Y2ConfigurationManagement::Widgets::Form) }
let(:locator) { locator_from_string(".root.person") }
let(:locator_1) { locator_from_string(".root.person.computers[0].brand") }
let(:locator) { locator_from_string("root#person") }
let(:locator_1) { locator_from_string("root#person#computers[0]#brand") }
let(:form) { Y2ConfigurationManagement::Salt::Form.from_file(FIXTURES_PATH.join("form.yml")) }
let(:pillar) { Y2ConfigurationManagement::Salt::Pillar.new }
let(:data) { Y2ConfigurationManagement::Salt::FormData.from_pillar(form, pillar) }
Expand Down
18 changes: 9 additions & 9 deletions test/y2configuration_management/salt/form_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@

let(:builder) { Y2ConfigurationManagement::Salt::FormBuilder.new(controller) }
let(:data) { Y2ConfigurationManagement::Salt::FormData.from_pillar(form, pillar) }
let(:locator) { locator_from_string(".root.person.computers") }
let(:collection_locator) { locator_from_string(".person.computers") }
let(:locator) { locator_from_string("root#person#computers") }
let(:collection_locator) { locator_from_string("person#computers") }
let(:popup) { instance_double(Y2ConfigurationManagement::Widgets::FormPopup, run: popup_run) }
let(:popup_run) { :ok }
let(:widget) do
Expand Down Expand Up @@ -135,15 +135,15 @@
).as_null_object
end

let(:parent_locator) { locator_from_string(".root.person.computers[0]") }
let(:parent_locator) { locator_from_string("root#person#computers[0]") }

before do
allow(builder).to receive(:build).and_return(widget)
state.open_form(parent_locator, parent_form)
end

context "when the user accepts the dialog" do
let(:collection_locator) { locator_from_string(".disks") }
let(:collection_locator) { locator_from_string("disks") }
let(:result) { { "type" => "HDD", "size" => "1TiB" } }

it "updates the form data" do
Expand Down Expand Up @@ -199,13 +199,13 @@
before do
allow(builder).to receive(:build).and_return(widget)
allow(data).to receive(:update).and_call_original
state.open_form(locator_from_string(".root.person.computers[1]"), parent_form)
state.open_form(locator_from_string("root#person#computers[1]"), parent_form)
end

context "when the user accepts the dialog" do
let(:collection_locator) { locator_from_string(".disks") }
let(:collection_locator) { locator_from_string("disks") }
let(:result) { { "type" => "HDD", "size" => "1TiB" } }
let(:disks_locator) { locator_from_string(".root.person.computers[1].disks") }
let(:disks_locator) { locator_from_string("root#person#computers[1]#disks") }

it "updates the form data" do
controller.add(collection_locator)
Expand All @@ -216,11 +216,11 @@
end

describe "#remove" do
let(:element_locator) { locator_from_string(".person.computers[1]") }
let(:element_locator) { locator_from_string("person#computers[1]") }

it "removes an element" do
expect { controller.remove(element_locator) }
.to change { controller.get(locator_from_string(".root.person.computers[1]")) }
.to change { controller.get(locator_from_string("root#person#computers[1]")) }
.from(Hash)
.to(nil)
end
Expand Down
10 changes: 5 additions & 5 deletions test/y2configuration_management/salt/form_data_reader_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

describe "#form_data" do
context "when a value is defined in the pillar" do
let(:locator) { locator_from_string(".root.person.name") }
let(:locator) { locator_from_string("root#person#name") }

it "uses the value from the pillar" do
form_data = reader.form_data
Expand All @@ -48,7 +48,7 @@
end

context "when a value is not defined in the pillar" do
let(:locator) { locator_from_string(".root.person.email") }
let(:locator) { locator_from_string("root#person#email") }

it "uses the default value from the form definition" do
form_data = reader.form_data
Expand All @@ -57,7 +57,7 @@
end

context "when a hash based collection is given" do
let(:locator) { locator_from_string(".root.person.projects") }
let(:locator) { locator_from_string("root#person#projects") }

it "converts it to an array of hashes adding a '$key' key" do
form_data = reader.form_data
Expand All @@ -68,7 +68,7 @@
end

context "when a simple hash based collection is given" do
let(:locator) { locator_from_string(".root.person.projects[0].properties") }
let(:locator) { locator_from_string("root#person#projects[0]#properties") }

it "converts it to an array of hashes adding '$key' and '$value' keys" do
form_data = reader.form_data
Expand All @@ -81,7 +81,7 @@
end

context "when a simple values based collection is given" do
let(:locator) { locator_from_string(".root.person.projects[0].platforms") }
let(:locator) { locator_from_string("root#person#projects[0]#platforms") }

it "keeps it as an array" do
form_data = reader.form_data
Expand Down

0 comments on commit d346f9a

Please sign in to comment.