class Reading::Csv::Parse

Parse is a function that parses CSV lines into item data (an array of hashes).

Public Class Methods

new(custom_config = {}) click to toggle source
# File lib/reading/csv/parse.rb, line 15
def initialize(custom_config = {})
  unless config
    @config = Reading.config.deeper_merge(custom_config)
    # if custom formats are given, use only the custom formats.
    if custom_config[:item] && custom_config[:item][:formats]
      config[:item][:formats] = custom_config[:item][:formats]
    end
    config.fetch(:csv).fetch(:columns)[:name] = true # Name column can't be disabled.
    Reading.add_regex_config(config)
  end
  @cur_line = nil
end

Public Instance Methods

call(feed = nil, path: nil, close_feed: true, selective: true, skip_compact_planned: false, &postprocess) click to toggle source
  • returns a hash of item data in the same order as they arrive from feed.

  • feed is anything with each_line.

  • if a block is given, parsing is stopped when it returns false.

  • postprocess can be used to convert the data hashes into Items. this way Item can access the CSV source line, which is useful since Item does additional validation on the data, and in case of any errors it can pass along the source line to an error message.

# File lib/reading/csv/parse.rb, line 35
def call(feed = nil,
          path: nil,
          close_feed: true,
          selective: true,
          skip_compact_planned: false,
          &postprocess)
  if feed.nil? && path.nil? && config.fetch(:csv).fetch(:path).nil?
    raise ArgumentError, "No file given to load."
  end
  feed ||= File.open(path || config.fetch(:csv).fetch(:path))
  items = []
  parse_regular = ParseRegularLine.new(config)
  parse_compact_planned = ParseCompactPlannedLine.new(config)
  feed.each_line do |line|
    line.force_encoding(Encoding::UTF_8)
    @cur_line = line.strip
    case line_type
    when :blank, :comment
      next
    when :regular
      items += parse_regular.call(cur_line, &postprocess)
    when :compact_planned_line
      next if skip_compact_planned
      items += parse_compact_planned.call(cur_line, &postprocess)
    end
    if selective
      continue = config.fetch(:csv).fetch(:selective_continue).call(items.last)
      case continue
      when false
        break
      when :skip
        items.pop
      end
    end
  end
  items
rescue Errno::ENOENT
  raise FileError.new(path, label: "File not found!")
rescue Errno::EISDIR
  raise FileError.new(path, label: "The reading list must be a file, not a directory!")
ensure
  feed&.close if close_feed && feed.respond_to?(:close)
  # reset to pre-call state.
  initialize
end

Private Instance Methods

compact_planned_line?() click to toggle source
# File lib/reading/csv/parse.rb, line 97
def compact_planned_line?
  cur_line.match?(config.fetch(:csv).fetch(:regex).fetch(:compact_planned_line_start))
end
line_type() click to toggle source
# File lib/reading/csv/parse.rb, line 83
def line_type
  return :blank if cur_line.empty?
  if starts_with_comment_character?
    return :compact_planned_line if compact_planned_line?
    return :comment
  end
  :regular
end
starts_with_comment_character?() click to toggle source
# File lib/reading/csv/parse.rb, line 92
def starts_with_comment_character?
  cur_line.start_with?(config.fetch(:csv).fetch(:comment_character)) ||
    cur_line.match?(/\A\s+#{config.fetch(:csv).fetch(:regex).fetch(:comment_escaped)}/)
end