buren / honey_format

Makes working with CSVs as smooth as honey.
MIT License
14 stars 2 forks source link

Allow simple has key => value header converter #53

Open buren opened 4 years ago

buren commented 4 years ago

In the readme we have an example of mapping a header column name to another name.

map = { 'First^Name' => :first_name }
converter = ->(column) { map.fetch(column, column.downcase) }

csv = "ID,First^Name\n1,Jacob"
user = HoneyFormat::CSV.new(csv, header_converter: converter)

It seems awfully long/boilerplatey for something that trivial and it has been a common use case for me. For example mapping name to city_name.

Instead we could allow

map = { 'First^Name' => :first_name }
user = HoneyFormat::CSV.new(csv, header_map: map)
# or we could detect that a hash is passed to header_converter
user = HoneyFormat::CSV.new(csv, header_converter: map)
buren commented 4 years ago

Diff that implements the suggested change

⚠️Draft code

diff --git a/lib/honey_format/matrix/header.rb b/lib/honey_format/matrix/header.rb
index 94bce6d..cb7fe4a 100644
--- a/lib/honey_format/matrix/header.rb
+++ b/lib/honey_format/matrix/header.rb
@@ -10,10 +10,11 @@ module HoneyFormat
     # Instantiate a Header
     # @return [Header] a new instance of Header.
     # @param [Array<String>] header array of strings.
-    # @param converter [#call, Symbol]
+    # @param converter [#call, Symbol, Hash]
     #   header converter that implements a #call method
     #   that takes one column (string) argument OR symbol for a registered
-    #   converter registry.
+    #   converter registry OR a hash mapped to a symbol or something that responds
+    #   to #call.
     # @param deduplicator [#call, Symbol]
     #   header deduplicator that implements a #call method
     #   that takes columns Array<String> argument OR symbol for a registered
@@ -101,6 +102,11 @@ module HoneyFormat
         return
       end

+      if object.respond_to?(:fetch)
+        @converter = hash_converter(object)
+        return
+      end
+      
       @converter = object
     end

@@ -164,5 +170,20 @@ module HoneyFormat
       ]
       raise(Errors::MissingHeaderColumnError, parts.join(' '))
     end
+
+    def hash_converter(hash)
+      ->(value) {
+        # support strings and symbol keys interchangeably
+        column = hash.fetch(value) do
+          key = value.respond_to?(:to_sym) ? value.to_sym : value
+          hash.fetch(key, value)
+        end
+
+        # The hash can contain mixed values, Symbol and procs
+        column = column.call(value) if column.respond_to?(:call)
+
+        column&.to_sym
+      }
+    end
   end
 end
diff --git a/spec/honey_format_spec.rb b/spec/honey_format_spec.rb
index d573b05..3c7c121 100644
--- a/spec/honey_format_spec.rb
+++ b/spec/honey_format_spec.rb
@@ -1,5 +1,7 @@
 # frozen_string_literal: true

 RSpec.describe HoneyFormat do
   it 'has a version number' do
     version_pattern = /\A
@@ -25,4 +27,24 @@ RSpec.describe HoneyFormat do
   it 'has a patch version number' do
     expect(HoneyFormat::PATCH_VERSION).not_to be nil
   end
+
+  it 'allows CSV header hash converter' do
+    csv_string = "username\nburen"
+    csv = HoneyFormat::CSV.new(csv_string, header_converter: { username: :handle })
+
+    expect(csv.rows.first.handle).to eq('buren')
+  end
+
+  it 'allows CSV header hash converter mixed with proc converters' do
+    csv_string = "username,first_name\nburen,jacob"
+    csv = HoneyFormat::CSV.new(
+      csv_string,
+      header_converter: {
+        username: :handle,
+        first_name: ->(v) { v.upcase } 
+      }
+    )
+    
+    expect(csv.rows.first.handle).to eq('buren')
+  end
 end