class CFA::AugeasWriter::LazyOperations
Represents an operation that needs to be done after all modifications.
The reason to have this class is that Augeas renumbers its arrays after some operations like `rm` or `insert` so previous paths are no longer valid. For this reason these sensitive operations that change paths need to be done at the end and with careful order. See www.redhat.com/archives/augeas-devel/2017-March/msg00002.html
@note This class depends on ordered operations. So adding and removing entries has to be done in order how they are placed in tree.
Attributes
Public Class Methods
@param aug result of Augeas.create
# File lib/cfa/augeas_parser/writer.rb, line 171 def initialize(aug) @aug = aug @operations = [] end
Public Instance Methods
# File lib/cfa/augeas_parser/writer.rb, line 176 def add(located_entry) @operations << { type: :add, located_entry: located_entry } end
# File lib/cfa/augeas_parser/writer.rb, line 180 def remove(located_entry) @operations << { type: :remove, path: located_entry.path } end
starts all previously inserted operations
# File lib/cfa/augeas_parser/writer.rb, line 185 def run # the reverse order is needed because if there are two operations # one after another then the latter cannot affect the former @operations.reverse_each do |operation| case operation[:type] when :remove then remove_entry(operation[:path]) when :add located_entry = operation[:located_entry] add_entry(located_entry) else raise "Invalid lazy operation #{operation.inspect}" end end end
Private Instance Methods
Adds entry to tree. At first it finds where to add it to be in correct place and then sets its value. Recursive if needed. In recursive case it is already known that whole sub-tree is also new and just added.
# File lib/cfa/augeas_parser/writer.rb, line 229 def add_entry(located_entry) path = insert_entry(located_entry) set_new_value(path, located_entry) end
Adds new subtree. Simplified version of common write as it is known that all entries will be just added. @param tree [CFA::AugeasTree] to add @param prefix [String] prefix where to place tree
# File lib/cfa/augeas_parser/writer.rb, line 256 def add_subtree(tree, prefix) tree.all_data.each do |entry| located_entry = LocatedEntry.new(tree, entry, prefix) # universal path that handles also new elements for arrays path = "#{prefix}/#{located_entry.key}[last()+1]" set_new_value(path, located_entry) end end
# File lib/cfa/augeas_parser/writer.rb, line 315 def find_preceding_index(paths, preceding) path = preceding.path # common case, just included return paths.index(path) if paths.include?(path) # not found, so it means that some collection or single entry switch new_path = +"/" path.split("/").each do |element| new_path << "/" unless new_path.end_with?("/") new_path << pick_candidate(paths, new_path, element) end paths.index(new_path) || raise("Cannot find path #{preceding.path} in #{paths.inspect}") end
Insert key after preceding. @see insert_entry
@param preceding [LocatedEntry] entry after which the new one goes @param located_entry [LocatedEntry] entry to insert @return [String] where value should be written.
# File lib/cfa/augeas_parser/writer.rb, line 295 def insert_after(preceding, located_entry) res = aug.insert(preceding.path, located_entry.key, false) # if insert failed it means, that previous preceding entry was single # element and now it is multiple ones, so try to first element as add # is done in reverse order if res == -1 # TODO: what about deep nesting of trees where upper level change # from single to collection? aug.insert(preceding.path + "[1]", located_entry.key, false) end path_after(preceding) end
It inserts a key at given position without setting its value. Its logic is to set it after the last valid entry. If it is not defined then tries to place it before the first valid entry in tree. If there is no entry in tree, then does not insert a position, which means that subsequent setting of value appends it to the end.
@param located_entry [LocatedEntry] entry to insert @return [String] where value should be written. Can
contain path expressions. See https://github.com/hercules-team/augeas/wiki/Path-expressions
# File lib/cfa/augeas_parser/writer.rb, line 275 def insert_entry(located_entry) # entries with add not exist yet preceding = located_entry.preceding_existing prefix = located_entry.prefix if preceding insert_after(preceding, located_entry) # entries with remove is already removed, otherwise find previously elsif located_entry.any_following? aug.insert(prefix + "/*[1]", located_entry.key, true) aug.match(prefix + "/*[1]").first else "#{prefix}/#{located_entry.key}" end end
Finds path immediately after preceding entry @param preceding [LocatedEntry]
# File lib/cfa/augeas_parser/writer.rb, line 310 def path_after(preceding) paths = aug.match(preceding.prefix + "/*") paths[find_preceding_index(paths, preceding) + 1] end
Finds path to remove, as path can be meanwhile renumbered, see remove_entry
# File lib/cfa/augeas_parser/writer.rb, line 216 def path_to_remove(path) if aug.match(path).size == 1 path elsif !aug.match(path + "[1]").empty? path + "[1]" else raise "Unknown augeas path #{path}" end end
it returns variant of element that exists in path
# File lib/cfa/augeas_parser/writer.rb, line 332 def pick_candidate(paths, new_path, element) # NOTE: order here is important due to early matching candidates = [element + "/", element + "[1]", element.sub(/\[\d+\]/, ""), element] paths.each do |p| candidates.each do |c| return c if p.start_with?(new_path + c) end end end
Removes entry from tree. If path does not exist, then tries if it has changed to a collection: If we remove and re-add a single key then because of the laziness Augeas will first see the addition, making a 2 member collection, so we need to remove “key” instead of “key”. @param path [String] original path name to remove
# File lib/cfa/augeas_parser/writer.rb, line 210 def remove_entry(path) aug.rm(path_to_remove(path)) end
Sets new value to given path. It is used for values that are not yet in Augeas tree. If needed it does recursive adding. @param path [String] path which can contain Augeas path expression for
key of new value
@param located_entry [LocatedEntry] entry to write @see github.com/hercules-team/augeas/wiki/Path-expressions
# File lib/cfa/augeas_parser/writer.rb, line 240 def set_new_value(path, located_entry) aug.set(path, located_entry.entry_value) # we need to get new path as path used in aug.set can contains # "[last() + 1]", so adding subtree to it, adds additional entry. # So here, we replace "[last() + 1]" with "[last()]" so it will match # path created by previous aug.set match_str = path.gsub(/\[\s*last\(\)\s*\+\s*1\]/, "[last()]") new_path = aug.match(match_str).first add_subtree(located_entry.entry_tree, new_path) end