Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions lib/sheetah/backends.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
# frozen_string_literal: true

require_relative "backends_registry"
require_relative "utils/monadic_result"

module Sheetah
module Backends
@registry = BackendsRegistry.new

SimpleError = Struct.new(:msg_code)
private_constant :SimpleError

class << self
attr_reader :registry

def open(*args, **opts, &block)
backend = opts.delete(:backend) || registry.get(*args, **opts)
backend = opts.delete(:backend)

if backend.nil?
return Utils::MonadicResult::Failure.new(SimpleError.new("no_applicable_backend"))
Expand Down
106 changes: 34 additions & 72 deletions lib/sheetah/backends/csv.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,52 +3,40 @@
require "csv"

require_relative "../sheet"
require_relative "../backends"

module Sheetah
module Backends
# Expect:
# - UTF-8 without BOM, or the correct encoding given explicitly
# - line endings as \n or \r\n
# - comma-separated
# - quoted with "
class Csv
include Sheet

class ArgumentError < Error
class InvalidCSVError < Error
end

class EncodingError < Error
end

CSV_OPTS = {
DEFAULTS = {
row_sep: :auto,
col_sep: ",",
quote_char: '"',
}.freeze

private_constant :CSV_OPTS

def self.register(registry = Backends.registry)
registry.set(self) do |args, opts|
next false unless args.empty?

case opts
in { io: _, **nil } | \
{ io: _, encoding: String | Encoding, **nil } | \
{ path: /\.csv$/i, **nil } | \
{ path: /\.csv$/i, encoding: String | Encoding, **nil }
then
true
else
false
end
end
private_constant :DEFAULTS

def self.defaults
DEFAULTS
end

def initialize(io: nil, path: nil, encoding: nil)
io = setup_io(io, path, encoding)
def initialize(
io,
row_sep: self.class.defaults[:row_sep],
col_sep: self.class.defaults[:col_sep],
quote_char: self.class.defaults[:quote_char]
)
@csv = CSV.new(
io,
row_sep: row_sep,
col_sep: col_sep,
quote_char: quote_char
)

@csv = CSV.new(io, **CSV_OPTS)
@headers = detect_headers(@csv)
@cols_count = @headers.size
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On obtient maintenant les options CSV depuis une méthode plutôt qu'une constante, c'est plus pratique.

Vu que c'est une méthode « de classe », cela nous oblige toujours à surcharger #initialize sauf erreur de ma part. En tout cas si on veut définir cette option dynamiquement en fonction de l'IO, qui n'est accessible que lorsque la classe est instanciée.

Cela permet d'utiliser super et simplifier un peu :
https://github.com/steeple-org/obelix/commit/416619d3ba94110dc02a018210e20ecaa91ff49d#diff-1fbd58936604235acab1b8ddf9ec5b3f038276a1c14e8ede2c2fcddfac03cf1b

Expand All @@ -68,62 +56,36 @@ def each_header
def each_row
return to_enum(:each_row) unless block_given?

@csv.each.with_index(1) do |raw, row|
value = Array.new(@cols_count) do |col_idx|
col = Sheet.int2col(col_idx + 1)
handle_malformed_csv do
@csv.each.with_index(1) do |raw, row|
value = Array.new(@cols_count) do |col_idx|
col = Sheet.int2col(col_idx + 1)

Cell.new(row: row, col: col, value: raw[col_idx])
end
Cell.new(row: row, col: col, value: raw[col_idx])
end

yield Row.new(row: row, value: value)
yield Row.new(row: row, value: value)
end
end

self
end

def close
@csv.close

nil
# Do nothing: this backend isn't responsible for opening the IO, and therefore it is not
# responsible for closing it either.
end

private

def setup_io(io, path, encoding)
if io.nil? && !path.nil?
setup_io_from_path(path, encoding)
elsif !io.nil? && path.nil?
setup_io_from_io(io, encoding)
else
raise ArgumentError, "Expected either IO or path"
end
end

def setup_io_from_io(io, encoding)
io.set_encoding(encoding, Encoding::UTF_8) if encoding
io
end

def setup_io_from_path(path, encoding)
opts = { mode: "r" }

if encoding
opts[:external_encoding] = encoding
opts[:internal_encoding] = Encoding::UTF_8
end

File.new(path, **opts)
def handle_malformed_csv
yield
rescue CSV::MalformedCSVError
raise InvalidCSVError
end

def detect_headers(csv)
headers =
begin
csv.shift
rescue CSV::MalformedCSVError
raise EncodingError
end

headers || []
handle_malformed_csv { csv.shift } || []
end
end
end
Expand Down
16 changes: 1 addition & 15 deletions lib/sheetah/backends/xlsx.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,13 @@
require "roo"

require_relative "../sheet"
require_relative "../backends"

module Sheetah
module Backends
class Xlsx
include Sheet

def self.register(registry = Backends.registry)
registry.set(self) do |args, opts|
next false unless args.empty?

case opts
in { path: /\.xlsx$/i, **nil }
true
else
false
end
end
end

def initialize(path:)
def initialize(path)
raise Error if path.nil?

@roo = Roo::Excelx.new(path)
Expand Down
27 changes: 0 additions & 27 deletions lib/sheetah/backends_registry.rb

This file was deleted.

Loading