Skip to content

Instantly share code, notes, and snippets.

@asterite
Created October 3, 2017 19:38
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save asterite/a04cb8920175c4b0fae42d88c4cce40d to your computer and use it in GitHub Desktop.
require "csv"
class CSV
macro mapping(mappings)
{% for key, value in mappings %}
{% mappings[key] = {type: value} unless value.is_a?(HashLiteral) %}
{% end %}
{% for key, mapping in mappings %}
property {{key}} : {{mapping[:type]}}
{% end %}
def initialize(
{% for key, mapping in mappings %}
@{{key.id}},
{% end %}
)
end
def self.from_csv(string)
rows = CSV.parse(string)
headers = rows.shift.map &.strip
objects = Array(self).new(rows.size)
rows.each do |row|
row = row.map &.strip
next if row.all? &.empty?
{% for key, mapping in mappings %}
__{{key.id}} = nil
{% end %}
row.each_with_index do |cell, i|
case headers[i]?
{% for key, mapping in mappings %}
when {{(mapping[:name] || key).id.stringify}}
{% if mapping[:nil_if_empty] %}
next if cell.empty?
{% end %}
__{{key.id}} = {{mapping[:type]}}.from_csv_cell(cell)
{% end %}
end
end
objects << new(
{% for key, mapping in mappings %}
{% if mapping[:nilable] %}
__{{key.id}},
{% else %}
if __{{key.id}}.nil?
raise "Missing CSV value for column '{{(mapping[:name] || key).id}}'"
else
__{{key.id}}.not_nil!
end,
{% end %}
{% end %}
)
end
objects
end
def self.to_csv(objects)
CSV.build do |csv|
csv.row(
{% for key, mapping in mappings %}
{{(mapping[:name] || key).id.stringify}},
{% end %}
)
objects.each &.to_csv(csv)
end
end
def to_csv(csv)
csv.row(
{% for key, mapping in mappings %}
if @{{key.id}}.nil?
nil
else
{{mapping[:type]}}.to_csv_cell(@{{key.id}}.not_nil!)
end,
{% end %}
)
end
end
end
struct Number
def self.from_csv_cell(string)
new(string)
end
def self.to_csv_cell(number)
new(number)
end
end
class String
def self.from_csv_cell(string)
string
end
def self.to_csv_cell(string)
string
end
end
class Array
def to_csv
T.to_csv(self)
end
def self.from_csv_cell(string)
T.from_csv_cell(string)
end
end
class Person
CSV.mapping({
name: String,
age: Int32,
})
end
csv = <<-CSV
name,age
Foo,10
Bar,20
CSV
people = Person.from_csv(csv)
p people
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment