lib/fat_table/table.rb in fat_table-0.9.3 vs lib/fat_table/table.rb in fat_table-0.9.5

- old
+ new

@@ -154,18 +154,22 @@ # will be taken from the first CSV row and converted to symbols. def self.from_csv_file(fname, **types) File.open(fname, 'r') do |io| from_csv_io(io, **types) end + rescue NoTable + raise NoTable, "no table found in CSV file '#{fname}'" end # :category: Constructors # Construct a Table from a CSV string +str+, treated in the same manner as # the input from a CSV file in ::from_org_file. - def self.from_csv_string(str, **types) - from_csv_io(StringIO.new(str), **types) + def self.from_csv_string(str, has_headers: true, **) + from_csv_io(StringIO.new(str), has_headers:, **) + rescue NoTable + raise NoTable, "no table found in string '#{str[0..20]}...'" end # :category: Constructors # Construct a Table from the first table found in the given Emacs org-mode @@ -174,18 +178,22 @@ # etc. are created. def self.from_org_file(fname, **types) File.open(fname, 'r') do |io| from_org_io(io, **types) end + rescue NoTable + raise NoTable, "no table found in file '#{fname}'" end # :category: Constructors # Construct a Table from a string +str+, treated in the same manner as the # contents of an org-mode file in ::from_org_file. def self.from_org_string(str, **types) from_org_io(StringIO.new(str), **types) + rescue NoTable + raise NoTable, "no table found in string '#{str[0..20]...}'" end # :category: Constructors # Construct a new table from an Array of Arrays +aoa+. By default, with @@ -273,13 +281,13 @@ private # Construct table from an array of hashes or an array of any object that # can respond to #to_h. If an array element is a nil, mark it as a group # boundary in the Table. - def from_array_of_hashes(hashes, hlines: false, **types) + def from_array_of_hashes(hashes, hlines: false, **) heads = hashes.first.keys - result = new(*heads, **types) + result = new(*heads, **) hashes.each do |hsh| if hsh.nil? unless hlines msg = 'found an hline in input: try setting hlines true' raise UserError, msg @@ -303,11 +311,11 @@ # element of the outer array is a nil, mark the preceding row as a group # boundary. Note: In org mode code blocks, by default (:hlines no) all # hlines are stripped from the table, otherwise (:hlines yes) they are # indicated with nil elements in the outer array as expected by this # method when hlines is set true. - def from_array_of_arrays(rows, hlines: false, **types) + def from_array_of_arrays(rows, hlines: false, **) headers = [] if !hlines # Take the first row as headers # Second row et seq as data headers = rows[0].map(&:to_s).map(&:as_sym) @@ -322,11 +330,11 @@ # Synthesize headers # Row 0 et seq are data headers = (1..rows[0].size).to_a.map { |k| "col_#{k}".as_sym } first_data_row = 0 end - result = new(*headers, **types) + result = new(*headers, **) rows[first_data_row..-1].each do |row| if row.nil? unless hlines msg = 'found an hline in input: try setting hlines true' raise UserError, msg @@ -340,28 +348,39 @@ end result.normalize_boundaries result end - def from_csv_io(io, **types) - result = new(**types) - ::CSV.new(io, headers: true, header_converters: :symbol, - skip_blanks: true).each do |row| - result << row.to_h + def from_csv_io(io, has_headers: true, **) + result = new(**) + if has_headers + ::CSV.new(io, headers: has_headers, header_converters: :symbol, skip_blanks: true).each do |row| + result << row.to_h + end + else + nfields = io.readline.split(',').size + io.seek(0, IO::SEEK_SET) + heads = (1..nfields).map {|n| "col_#{n}"} + ::CSV.new(io, headers: heads, skip_blanks: true).each do |row| + result << row.to_h + end end result.normalize_boundaries result + rescue StandardError + raise NoTable end # Form rows of table by reading the first table found in the org file. The # header row must be marked with an hline (i.e, a row that looks like # '|---+--...--|') and groups of rows may be marked with hlines to # indicate group boundaries. - def from_org_io(io, **types) + def from_org_io(io, **) table_re = /\A\s*\|/ hrule_re = /\A\s*\|[-+]+/ rows = [] + ncols = nil table_found = false header_found = false io.each do |line| unless table_found # Skip through the file until a table is found @@ -386,14 +405,21 @@ elsif !line.match?(table_re) # Stop reading at the second hline break else line = line.sub(/\A\s*\|/, '').sub(/\|\s*\z/, '') - rows << line.split('|').map(&:clean) + # Don't include any rows with a size different from that + # established by the header row. + cols = line.split('|').map(&:clean) + ncols ||= cols.size + next unless cols.size == ncols + + rows << cols end end - from_array_of_arrays(rows, hlines: true, **types) + raise NoTable unless table_found + from_array_of_arrays(rows, hlines: true, **) end end ########################################################################### # Attributes @@ -715,9 +741,10 @@ def normalize_boundaries unless empty? self.explicit_boundaries = explicit_boundaries.uniq.sort end explicit_boundaries + self end # Return the explicit_boundaries, augmented by an implicit boundary for # the end of the table, unless it's already an implicit boundary. def boundaries