module Eco::Data::Locations module Convert include Eco::Language::AuxiliarLogger # Helper to open a csv # @note this is a shortcut helper. # @param filename [String] the csv file. # @return [Eco::CSV::Table] def csv_from(filename, encoding: 'utf-8') raise ArgumentError, "Expecting String filename. Given: #{filename.class}" unless filename.is_a?(String) raise "Missing #{filename}" unless File.exist?(filename) Eco::CSV.read(filename, encoding: encoding) rescue CSV::MalformedCSVError => e if match = e.message.match(/line (?\d+)/i) log(:error) {"An encoding problem was found on line #{match[:line]}"} end raise end # Generic converter/helper to generate the csv data export for a hierarchical csv tree # @note The steps of usage would be: # 1. First **treeify** your input (i.e. `Eco::API::Organization::TagTree#as_json`, # or `treeify(nodes)` # @yield [str_node, node] block for custom output node name # @yiledreturn [String] the node name # @param hash_nodes [Array] a hierarchical tree of Hash nodes, nested via `nodes` # @return [CSV::Table] ready to be made a hierarchical csv tree (i.e. out.to_csv) def hash_tree_to_tree_csv(hash_nodes, out: [], done_ids: [], repeated_ids: [], level: 0, attrs: [:id]) lev = level + 1 base = empty_array(level) sattrs = attrs.map(&:to_s) hash_nodes.each_with_object(out) do |node, out| if done_ids.include?(id = node["id"]) repeated_ids << id else has_offspring = (children = node["nodes"]) && !children.empty? done_ids << id str_node = node.values_at(*sattrs).join(" ## ") out << (base.dup << str_node) hash_tree_to_tree_csv(node["nodes"], out: out, done_ids: done_ids, repeated_ids: repeated_ids, level: lev, attrs: attrs) end end.tap do |out| if level == 0 report_repeated_node_ids(repeated_ids) return Eco::CSV::Table.new(normalize_arrays(out)) end end end # It normalizes the size of the arrays to the max size among the arrays # @param rows [Array] where arrays may not have the same length # @return [Array] where arrays have all the same length def normalize_arrays(rows) max_row = rows.max {|a, b| a.length <=> b.length} holder = empty_array(max_row.length) rows.map do |row| row.dup.concat(holder[0..-(row.length+1)]) end end # @param count [Integer] number of possitions of the new array # @return [Array] with `count` positions. def empty_array(count) Array.new(count, nil) end # @note # 1. Initially it has as many keys as levels `count` # 2. It serves the purpose to track the lastest seen node # for a given level, during a loop. # @return [Hash] with integer level counts as keys and # nodes as values. def empty_level_tracker_hash(count = 11) Array(1..count).zip(empty_array(count)).to_h end # It logs a message from `yield` and appends a `pretty_inspect` on object. # @note it only works where `object` is `Enumerable` def log_pretty_inspect(object, lev = :info) return unless object.is_a?(Enumerable) return if object.empty? msg = '' msg << "#{yield(object)}\n" if block_given? msg << object.pretty_inspect log(lev) { msg } end # Prints a common message def report_repeated_node_ids(repeated) log_pretty_inspect(repeated, :warn) do "There were #{repeated.length} repeated node ids. Only one included. These excluded:" end end end end