class Eco::API::UseCases::Default::Utils::SortCsv < Eco::API::Custom::UseCase name 'sort-csv' type :other require_relative 'cli/sort_csv_cli' def main(*_args) if simulate? count = Eco::CSV.count(input_file) log(:info) { "CSV '#{input_file}' has #{count} rows." } else group_input_rows generate_file end end private attr_reader :headers, :headers_rest def group_input_rows idx = 0 first = true Eco::CSV.foreach(input_file, headers: true, skip_blanks: true) do |row| idx += 1 if first first = false @output_headers = row.headers require_sort_field!(row, file: input_file) end pivot_value = row[sort_field] (row_groups[pivot_value] ||= []) << row if (idx % 500).zero? print "... Tracked #{idx} rows \r" $stdout.flush end end ensure log(:info) { "Tracked #{idx} rows"} end def generate_file idx = 0 CSV.open(output_filename, 'wb') do |csv| csv << @output_headers row_groups.keys.sort.each do |key| row_groups[key].each do |row| csv << row.values_at(*@output_headers) idx += 1 if (idx % 500).zero? print "... Sorted #{idx} rows \r" $stdout.flush end end end end ensure msg = "Generated file '#{output_filename}' with #{idx} rows." log(:info) { msg } unless simulate? end def row_groups @row_groups ||= {} end def output_filename return nil unless input_name File.join(input_dir, "#{input_name}_sorted#{input_ext}") end def input_name @input_name ||= File.basename(input_basename, input_ext) end def input_ext @input_ext ||= input_basename.split('.')[1..].join('.').then do |name| ".#{name}" end end def input_basename @input_basename ||= File.basename(input_full_filename) end def input_dir @input_dir = File.dirname(input_full_filename) end def input_full_filename @input_full_filename ||= File.expand_path(input_file) end def input_file options.dig(:input, :file) end def require_sort_field!(row, file:) return true if row.key?(sort_field) msg = "Sort field '#{sort_field}' missing in header of file '#{file}'" log(:error) { msg } raise msg end def sort_field @sort_field ||= opts_sort_by.tap do |pivot| next if pivot msg = "The pivot field should be specified with -by option" log(:error) { msg } raise msg end end def opts_sort_by options.dig(:input, :sort_by) end end