# frozen_string_literal: true module HoneyFormat # Represents Matrix. class Matrix # Instantiate Matrix. # @return [Matrix] a new instance of Matrix. # @param [Array>] matrix # @param header [Array] # header optional argument that represents header, required if the matrix # lacks a header row. # @param header_converter [#call] converts header columns. # @param header_deduplicator [#call] deduplicates header columns. # @param row_builder [#call] will be called for each parsed row. # @param type_map [Hash] map of column_name => type conversion to perform. # @raise [HeaderError] super class of errors raised when there is a header error. # @raise [MissingHeaderError] raised when header is missing (empty or nil). # @raise [MissingHeaderColumnError] raised when header column is missing. # @raise [RowError] super class of errors raised when there is a row error. # @raise [EmptyRowColumnsError] raised when row columns are empty. # @raise [InvalidRowLengthError] raised when row has more columns than header columns. # @example # matrix = HoneyFormat::Matrix.new([%w[name id], %w[jacob 1]]) # matrix.columns # => [:name, :id] # matrix.rows.to_a # => [#] # @example With custom header converter # converter = proc { |v| v == 'name' ? 'first_name' : v } # matrix = HoneyFormat::Matrix.new([%w[name id]], header_converter: converter) # matrix.columns # => [:first_name, :id] # @example Handle errors # begin # matrix = HoneyFormat::Matrix.new([%w[name id]]) # rescue HoneyFormat::HeaderError => e # puts "header error: #{e.class}, #{e.message}" # rescue HoneyFormat::RowError => e # puts "row error: #{e.class}, #{e.message}" # end # @see Header#initialize # @see Rows#initialize def initialize( matrix, header: nil, header_converter: HoneyFormat.header_converter, header_deduplicator: HoneyFormat.config.header_deduplicator, row_builder: nil, type_map: {} ) header_row = header || matrix.shift @header = Header.new( header_row, converter: header_converter, deduplicator: header_deduplicator ) @type_map = type_map.select { |key, _v| @header.columns.include?(key) }.to_h @rows = Rows.new(matrix, columns, builder: row_builder, type_map: @type_map) end # Original matrix header # @return [Header] object representing the matrix header. def header @header end # Matrix columns converted from the original Matrix header # @return [Array] of column identifiers. def columns @header.to_a end # Matrix type map used # @return [Hash] the type map used. def type_map @type_map end # Return rows # @return [Rows] rows. def rows @rows end # Itereate over each row # @yield [row] The given block will be passed for every row. # @yieldparam [Row] row in the matrix. # @return [Enumerator] If no block is given, an enumerator object will be returned. def each_row return rows.each unless block_given? rows.each { |row| yield(row) } end # Convert matrix to CSV-string. # @param columns [Array, Set, NilClass] # the columns to output, nil means all columns (default: nil) # @yield [row] # The given block will be passed for every row - return truthy if you want the # row to be included in the output # @yieldparam [Row] row # @return [String] CSV-string representation. # @example with selected columns # matrix.to_csv(columns: [:id, :country]) # @example with selected rows # matrix.to_csv { |row| row.country == 'Sweden' } # @example with both selected columns and rows # matrix.to_csv(columns: [:id, :country]) { |row| row.country == 'Sweden' } def to_csv(columns: nil, &block) columns = columns&.map(&:to_sym) @header.to_csv(columns: columns) + @rows.to_csv(columns: columns, &block) end end end