Files
rdb/lib/rdb/table.rb
T
wkh 75c2424307 Fehler behoben: Umlaute, Latin-1, Fehlermeldungen, atomares Schreiben
Spaltennamen mit Umlauten in Ausdrücken, Latin-1-Dateien als UTF-8,
Fehler als eine Zeile (RDB_DEBUG=1 für Backtrace), Werte mit Tab oder
Zeilenumbruch abgelehnt, Tabellen und Indexe über Temp-Datei und rename,
update.rdb gibt den Satz bei Editorfehler zurück, sorttable -r stabil.
2026-10-30 09:48:42 +01:00

110 lines
2.9 KiB
Ruby

# frozen_string_literal: true
module Rdb
# A table is a head line of column names, a dash line, then one
# tab-separated row per line. A list is the same data turned sideways:
# it starts with a newline and holds "Column<tab>value" lines.
class Table
attr_reader :head, :rows
attr_accessor :list
def initialize(head, rows, list: false)
@head = head
@rows = rows
@list = list
end
def self.dash_line?(cells)
cells.all? { |c| c.match?(/\A\s*-*\s*\z/) } && cells.any? { |c| c.include?('-') }
end
# COHERENT-era files are often Latin-1. Text that is not valid UTF-8 is
# read as Latin-1 and comes out as UTF-8.
def self.utf8(text)
return text if text.encoding == Encoding::UTF_8 && text.valid_encoding?
text.dup.force_encoding(Encoding::UTF_8).then do |t|
t.valid_encoding? ? t : text.dup.force_encoding(Encoding::ISO_8859_1).encode(Encoding::UTF_8)
end
end
def self.parse(text)
text = utf8(text)
return parse_list(text) if text.start_with?("\n")
lines = text.lines.map { |l| l.chomp("\n") }
# justify pads the names too ("Name "); they still name the column
head = (lines.shift || '').split("\t", -1).map(&:strip)
lines.shift if lines.first && dash_line?(lines.first.split("\t", -1))
rows = lines.map { |l| l.split("\t", -1) }
new(head, rows.map { |r| r.fill('', r.size...head.size) })
end
def self.parse_list(text)
head = []
records = []
rec = nil
text.sub(/\A\n/, '').each_line do |line|
line = line.chomp("\n")
if line.strip.empty?
rec = nil
next
end
name, value = line.split("\t", 2)
value ||= ''
head << name unless head.include?(name)
rec = nil if rec && name == head.first && rec.key?(name)
unless rec
rec = {}
records << rec
end
rec[name] = value
end
new(head, records.map { |r| head.map { |h| r.fetch(h, '') } }, list: true)
end
def self.read(io)
parse(io.read)
end
def self.open(path)
path == '-' ? read($stdin) : parse(File.read(path))
end
def index_of(name)
Rdb.column_index(@head, name) || Rdb.die("no column named '#{name}'")
end
def to_s
return list_text if @list
out = +''
out << @head.join("\t") << "\n"
out << Table.dash_row(@head) << "\n"
@rows.each { |r| out << r.join("\t") << "\n" }
out
end
def self.dash_row(head)
head.map { |h| '-' * [h.size, 1].max }.join("\t")
end
def list_text
out = +"\n"
@rows.each do |r|
@head.each_with_index { |h, i| out << h << "\t" << r[i].to_s << "\n" }
out << "\n"
end
out
end
def write(out = $stdout)
out.write to_s
end
def with(head: @head, rows: @rows)
Table.new(head, rows, list: @list)
end
end
end