Hello community, here is the log from the commit of package rubygem-cfa for openSUSE:Factory checked in at 2016-10-18 13:25:57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/rubygem-cfa (Old) and /work/SRC/openSUSE:Factory/.rubygem-cfa.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "rubygem-cfa" Changes: -------- --- /work/SRC/openSUSE:Factory/rubygem-cfa/rubygem-cfa.changes 2016-06-02 12:48:45.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.rubygem-cfa.new/rubygem-cfa.changes 2016-10-18 13:25:57.000000000 +0200 @@ -1,0 +2,14 @@ +Tue Oct 11 14:58:24 UTC 2016 - jreidin...@suse.com + +- optimize loading configuration files with augeas by reducing + number of augeas match calls (bsc#877047) +- 0.4.1 + +------------------------------------------------------------------- +Tue Sep 27 09:17:51 UTC 2016 - jreidin...@suse.com + +- support augeas nodes containing value and also attached tree + below it like e.g. ntp.conf has (bnc#983486) +- 0.4.0 + +------------------------------------------------------------------- Old: ---- cfa-0.3.1.gem New: ---- cfa-0.4.1.gem ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ rubygem-cfa.spec ++++++ --- /var/tmp/diff_new_pack.J7wIID/_old 2016-10-18 13:25:58.000000000 +0200 +++ /var/tmp/diff_new_pack.J7wIID/_new 2016-10-18 13:25:58.000000000 +0200 @@ -17,7 +17,7 @@ Name: rubygem-cfa -Version: 0.3.1 +Version: 0.4.1 Release: 0 %define mod_name cfa %define mod_full_name %{mod_name}-%{version} ++++++ cfa-0.3.1.gem -> cfa-0.4.1.gem ++++++ Files old/checksums.yaml.gz and new/checksums.yaml.gz differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/cfa/augeas_parser.rb new/lib/cfa/augeas_parser.rb --- old/lib/cfa/augeas_parser.rb 2016-05-24 16:16:10.000000000 +0200 +++ new/lib/cfa/augeas_parser.rb 2016-10-12 16:39:53.000000000 +0200 @@ -47,6 +47,24 @@ end end + # Represents node that contain value and also subtree below it + # For easier traversing it pass #[] to subtree + class AugeasTreeValue + # value in node + attr_accessor :value + # subtree below node + attr_accessor :tree + + def initialize(tree, value) + @tree = tree + @value = value + end + + def [](value) + tree[value] + end + end + # Represent parsed augeas config tree with user friendly methods class AugeasTree # low level access to augeas structure @@ -64,12 +82,22 @@ @data.reject! { |entry| entry[:key] == key } end + # adds the given value for the key in tree. + # @param value can be value of node, {AugeasTree} + # attached to key or its combination as {AugeasTreeValue} + # @param placer object determining where to insert value in tree. + # Useful e.g. to specify order of keys or placing comment above of given + # key. def add(key, value, placer = AppendPlacer.new) element = placer.new_element(self) element[:key] = key element[:value] = value end + # finds given value in tree. + # @return It can return value of node, {AugeasTree} + # attached to key or its combination as {AugeasTreeValue}. + # Also nil can be returned if key not found. def [](key) entry = @data.find { |d| d[:key] == key } return entry[:value] if entry @@ -77,6 +105,8 @@ nil end + # Sets the given value for the key in tree. It can be value of node, + # {AugeasTree} attached to key or its combination as {AugeasTreeValue} def []=(key, value) entry = @data.find { |d| d[:key] == key } if entry @@ -95,13 +125,12 @@ # @note for internal usage only # @private - def load_from_augeas(aug, prefix) - matches = aug.match("#{prefix}/*") - - @data = matches.map do |aug_key| + def load_from_augeas(aug, prefix, keys_cache) + @data = keys_cache.keys_for_prefix(prefix).map do |key| + aug_key = prefix + "/" + key { key: load_key(prefix, aug_key), - value: load_value(aug, aug_key) + value: load_value(aug, aug_key, keys_cache) } end end @@ -112,35 +141,27 @@ arrays = {} @data.each do |entry| - aug_key = obtain_aug_key(prefix, entry, arrays) - if entry[:value].is_a? AugeasTree - entry[:value].save_to_augeas(aug, aug_key) - else - report_error(aug) unless aug.set(aug_key, entry[:value]) - end + save_entry(entry[:key], entry[:value], arrays, aug, prefix) end end - # @note for debugging purpose only - def dump_tree(prefix = "") - arrays = {} + private - @data.each_with_object("") do |entry, res| - aug_key = obtain_aug_key(prefix, entry, arrays) - if entry[:value].is_a? AugeasTree - res << entry[:value].dump_tree(aug_key) - else - res << aug_key << "\n" - end + def save_entry(key, value, arrays, aug, prefix) + aug_key = obtain_aug_key(prefix, key, arrays) + case value + when AugeasTree then value.save_to_augeas(aug, aug_key) + when AugeasTreeValue + report_error(aug) unless aug.set(aug_key, value.value) + value.tree.save_to_augeas(aug, aug_key) + else + report_error(aug) unless aug.set(aug_key, value) end end - private - - def obtain_aug_key(prefix, entry, arrays) - key = entry[:key] + def obtain_aug_key(prefix, key, arrays) if key.end_with?("[]") - array_key = key.sub(/\[\]$/, "") + array_key = key[0..-3] # remove trailing [] arrays[array_key] ||= 0 arrays[array_key] += 1 key = array_key + "[#{arrays[array_key]}]" @@ -156,18 +177,23 @@ end def load_key(prefix, aug_key) - key = aug_key.sub(/^#{Regexp.escape(prefix)}\//, "") - key.sub(/\[\d+\]$/, "[]") + # clean from key prefix and for collection remove number inside [] + # +1 for size due to ending '/' not part of prefix + key = aug_key[(prefix.size + 1)..-1] + key.end_with?("]") ? key.sub(/\[\d+\]$/, "[]") : key end - def load_value(aug, aug_key) - nested = !aug.match("#{aug_key}/*").empty? + def load_value(aug, aug_key, keys_cache) + subkeys = keys_cache.keys_for_prefix(aug_key) + + nested = !subkeys.empty? + value = aug.get(aug_key) if nested subtree = AugeasTree.new - subtree.load_from_augeas(aug, aug_key) - subtree + subtree.load_from_augeas(aug, aug_key, keys_cache) + value ? AugeasTreeValue.new(subtree, value) : subtree else - aug.get(aug_key) + value end end end @@ -186,6 +212,7 @@ @lens = lens end + # parses given string and returns AugeasTree instance def parse(raw_string) @old_content = raw_string @@ -196,13 +223,16 @@ aug.set("/input", raw_string) report_error(aug) unless aug.text_store(@lens, "/input", "/store") + keys_cache = AugeasKeysCache.new(aug) + tree = AugeasTree.new - tree.load_from_augeas(aug, "/store") + tree.load_from_augeas(aug, "/store", keys_cache) return tree end end + # Serializes AugeasTree instance into returned string def serialize(data) # open augeas without any autoloading and it should not touch disk and # load lenses as needed only @@ -228,13 +258,62 @@ def report_error(aug) error = aug.error # zero is no error, so problem in lense - if aug.error[:code] != 0 + if aug.error[:code].nonzero? raise "Augeas error #{error[:message]}. Details: #{error[:details]}." - else - msg = aug.get("/augeas/text/store/error/message") - location = aug.get("/augeas/text/store/error/lens") - raise "Augeas parsing/serializing error: #{msg} at #{location}" end + + msg = aug.get("/augeas/text/store/error/message") + location = aug.get("/augeas/text/store/error/lens") + raise "Augeas parsing/serializing error: #{msg} at #{location}" + end + end +end + +# Cache that holds all avaiable keys in augeas tree. It is used to +# prevent too many aug.match calls which are expensive. +class AugeasKeysCache + STORE_PREFIX = "/store".freeze + STORE_LEN = STORE_PREFIX.size + STORE_LEN_1 = STORE_LEN + 1 + + # initialize cache from passed augeas object + def initialize(aug) + fill_cache(aug) + end + + # returns list of keys available on given prefix + def keys_for_prefix(prefix) + cut = prefix.length > STORE_LEN ? STORE_LEN_1 : STORE_LEN + path = prefix[cut..-1] + path = path.split("/") + matches = path.reduce(@cache) { |a, e| a[e] } + + matches.keys + end + +private + + def fill_cache(aug) + @cache = {} + search_path = "#{STORE_PREFIX}/*" + loop do + matches = aug.match(search_path) + break if matches.empty? + assign_matches(matches, @cache) + + search_path += "/*" + end + end + + def assign_matches(matches, cache) + matches.each do |match| + path = match[STORE_LEN_1..-1].split("/") + leap = path.pop + target = path.reduce(cache) do |acc, p| + acc[p] + end + + target[leap] = {} end end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/metadata new/metadata --- old/metadata 2016-05-24 16:16:14.000000000 +0200 +++ new/metadata 2016-10-12 16:39:53.000000000 +0200 @@ -1,14 +1,14 @@ --- !ruby/object:Gem::Specification name: cfa version: !ruby/object:Gem::Version - version: 0.3.1 + version: 0.4.1 platform: ruby authors: - Josef Reidinger autorequire: bindir: bin cert_chain: [] -date: 2016-05-24 00:00:00.000000000 Z +date: 2016-10-12 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: ruby-augeas @@ -58,7 +58,7 @@ version: 1.3.6 requirements: [] rubyforge_project: -rubygems_version: 2.4.5.1 +rubygems_version: 2.2.2 signing_key: specification_version: 4 summary: CFA (Config Files API) provides an easy way to create models on top of configuration