Version 0.2.0: RDB_ICASE, Changelog, --version, angepasste Man-Pages
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
# Changelog
|
||||
|
||||
Alle wichtigen Änderungen stehen hier. Das Format folgt
|
||||
[Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die Versionsnummern
|
||||
folgen [Semantic Versioning](https://semver.org/lang/de/). Die Version steht in
|
||||
`lib/rdb/version.rb`; `rdb --version` zeigt sie an.
|
||||
|
||||
## [Unveröffentlicht]
|
||||
|
||||
## [0.2.0] - 2026-10-11
|
||||
|
||||
### Hinzugefügt
|
||||
- `RDB_ICASE=1` ignoriert Groß-/Kleinschreibung bei Spaltennamen. Ein exakter
|
||||
Treffer gewinnt, mehrere Treffer sind ein Fehler; awk-Wörter und
|
||||
Funktionsaufrufe bleiben unberührt.
|
||||
- Man-Pages von `row`, `compute`, `column`, `sorttable`, `jointable`, `rename`,
|
||||
`validate` und `rdb` mit dem Abschnitt „Case of Column Names“.
|
||||
- `rdb man -g`: Man-Page mit groff formatiert, dazu `man/rdb.tmac` mit den
|
||||
MWC-Makros (`.DM`, `.DE`, `.PC` ...).
|
||||
- `rdb --version`, `CHANGELOG.md`.
|
||||
|
||||
### Geändert
|
||||
- `column` schreibt den Kopf in der Schreibweise der Tabelle, wenn die Spalte
|
||||
existiert.
|
||||
- `demo/ledger` und `demo/chart` verwenden echte Tabs.
|
||||
|
||||
## [0.1.0] - 2026-10-11
|
||||
|
||||
### Hinzugefügt
|
||||
- Erster Nachbau von /rdb in Ruby: ein Programm `bin/rdb` mit Symlinks für alle
|
||||
dokumentierten Befehle (Relational, Text, Konvertierung, Schnellzugriff,
|
||||
Berichte, Terminal, /act), 137 Man-Pages, Demo-Daten, 76 Tests, mise-Setup.
|
||||
@@ -43,11 +43,19 @@ die meisten Befehle lesen beides.
|
||||
`row`, `compute` und `validate` übergeben ihren Ausdruck an awk, nachdem sie
|
||||
Spaltennamen durch `$N` ersetzt haben. Es gilt also awk-Syntax.
|
||||
|
||||
Spaltennamen unterscheiden Groß- und Kleinschreibung. Mit `RDB_ICASE=1` wird
|
||||
sie ignoriert (Details unter „Unterschiede zum Original“):
|
||||
|
||||
```
|
||||
RDB_ICASE=1 compute 'wert = cost * amount' < inventory
|
||||
```
|
||||
|
||||
## Hilfe
|
||||
|
||||
```
|
||||
rdb alle Befehle
|
||||
rdb man search die Original-Man-Page als Text (auch Themen: backup, dbase, dashline ...)
|
||||
rdb --version Versionsnummer (Änderungen siehe CHANGELOG.md)
|
||||
rdb man -g search dieselbe Seite mit groff formatiert (Beispiele sauber eingerückt)
|
||||
whatis column Beschreibung und Syntax
|
||||
whatwill total Befehle, die zu einem Stichwort passen
|
||||
@@ -104,6 +112,14 @@ er nicht mehr passt (außer mit `-a`). Beide halten die Indizes aktuell.
|
||||
Handbuch sind als Tests enthalten (`test/rdb_test.rb`).
|
||||
- Die Indexdateien haben ein eigenes, einfaches Format (das Original ist nicht
|
||||
dokumentiert). Sie sind nicht mit alten /rdb-Dateien austauschbar.
|
||||
- Spaltennamen unterscheiden wie im Original Groß- und Kleinschreibung. Mit
|
||||
`RDB_ICASE=1` (z. B. in `mise.toml` unter `[env]`) wird sie ignoriert:
|
||||
ein exakter Treffer gewinnt, mehrere ähnliche sind ein Fehler. In
|
||||
`row`/`compute`/`validate` bleiben awk-Wörter und Funktionsaufrufe unberührt
|
||||
(eine Spalte `Length` verdeckt also nicht `length()`). Die Man-Pages von
|
||||
`row`, `compute`, `column`, `sorttable`, `jointable`, `rename`, `validate` und
|
||||
`rdb` haben dazu den Abschnitt „Case of Column Names“; er ist die einzige
|
||||
Ergänzung gegenüber den Original-Seiten.
|
||||
- `jointable` braucht keine vorsortierten Tabellen.
|
||||
- `ve` ist ein zeilenorientierter Editor, kein Bildschirmeditor.
|
||||
- `soundex` nutzt den Standard-Algorithmus. Die Beispielwerte im Handbuch
|
||||
|
||||
@@ -12,6 +12,7 @@ Signal.trap('PIPE', 'SYSTEM_DEFAULT')
|
||||
if name == 'rdb'
|
||||
case args.first
|
||||
when nil, '-h', '--help' then name = 'rdb.list'
|
||||
when '-v', '--version' then puts "rdb #{Rdb::VERSION}"; exit
|
||||
when 'install' then name = 'rdb.install'; args.shift
|
||||
else name = args.shift
|
||||
end
|
||||
|
||||
+16
@@ -7,6 +7,7 @@ require 'open3'
|
||||
require 'date'
|
||||
require 'tmpdir'
|
||||
require 'fileutils'
|
||||
require_relative 'rdb/version'
|
||||
|
||||
module Rdb
|
||||
ROOT = File.expand_path('..', __dir__)
|
||||
@@ -16,6 +17,21 @@ module Rdb
|
||||
COMMANDS = {}
|
||||
|
||||
class << self
|
||||
# RDB_ICASE=1 makes column names match without regard to case. An exact
|
||||
# match always wins; several case-insensitive matches are an error.
|
||||
def icase?
|
||||
!%w[0 no false].include?(ENV['RDB_ICASE'].to_s.downcase) && !ENV['RDB_ICASE'].to_s.empty?
|
||||
end
|
||||
|
||||
def column_index(head, name)
|
||||
exact = head.index(name)
|
||||
return exact if exact || !icase?
|
||||
|
||||
hits = head.each_index.select { |i| head[i].casecmp?(name) }
|
||||
die "column name '#{name}' is ambiguous: #{hits.map { |i| head[i] }.join(', ')}" if hits.size > 1
|
||||
hits.first
|
||||
end
|
||||
|
||||
def die(msg)
|
||||
abort "#{File.basename($PROGRAM_NAME)}: #{msg}"
|
||||
end
|
||||
|
||||
@@ -15,8 +15,9 @@ Rdb.register('column', 'Display columns of a table in any order', 'column [ Colu
|
||||
t = table_in
|
||||
next t.write if args.empty?
|
||||
|
||||
idx = args.map { |a| t.head.index(a) }
|
||||
t.with(head: args, rows: t.rows.map { |r| idx.map { |i| i ? r[i] : '' } }).write
|
||||
idx = args.map { |a| Rdb.column_index(t.head, a) }
|
||||
head = args.each_with_index.map { |a, n| idx[n] ? t.head[idx[n]] : a }
|
||||
t.with(head: head, rows: t.rows.map { |r| idx.map { |i| i ? r[i] : '' } }).write
|
||||
end
|
||||
Rdb.alias_for('project', 'column')
|
||||
|
||||
|
||||
@@ -40,6 +40,24 @@ module Rdb
|
||||
|
||||
# Replace column names in an awk expression with $N, leaving strings,
|
||||
# regexes and other identifiers alone.
|
||||
AWK_WORDS = %w[BEGIN END NR NF FS OFS ORS RS FNR FILENAME SUBSEP RSTART RLENGTH ENVIRON ARGC ARGV
|
||||
if else while for do in break continue next exit return function delete getline
|
||||
print printf length substr index split sub gsub match sprintf sin cos atan2 exp log
|
||||
sqrt int rand srand tolower toupper system].freeze
|
||||
|
||||
# With RDB_ICASE, a word that is not an awk keyword or function call may
|
||||
# name a column in any case, provided that exactly one column fits.
|
||||
def icase_column(names, expr, i)
|
||||
return unless Rdb.icase?
|
||||
|
||||
word = expr[i..][/\A\w+/].to_s
|
||||
return if word.empty? || AWK_WORDS.include?(word) || expr[i + word.size..].to_s.match?(/\A\s*\(/)
|
||||
|
||||
hits = names.select { |n, _| n.casecmp?(word) }
|
||||
Rdb.die "column name '#{word}' is ambiguous: #{hits.map(&:first).join(', ')}" if hits.size > 1
|
||||
hits.first
|
||||
end
|
||||
|
||||
def to_awk(expr, head)
|
||||
names = head.each_with_index.sort_by { |n, _| -n.size }
|
||||
out = +''
|
||||
@@ -59,6 +77,7 @@ module Rdb
|
||||
i = j + 1
|
||||
elsif c.match?(/[A-Za-z_#]/) && (i.zero? || !expr[i - 1].match?(/[\w$]/))
|
||||
hit = names.find { |n, _| !n.empty? && expr[i, n.size] == n && !expr[i + n.size].to_s.match?(/\w/) }
|
||||
hit ||= icase_column(names, expr, i)
|
||||
if hit
|
||||
out << "$#{hit[1] + 1}"
|
||||
i += hit[0].size
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ module Rdb
|
||||
end
|
||||
|
||||
def column(name)
|
||||
@head.index(name) || Rdb.die("#{path}: no column named '#{name}'")
|
||||
Rdb.column_index(@head, name) || Rdb.die("#{path}: no column named '#{name}'")
|
||||
end
|
||||
|
||||
def row_at(offset)
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ module Rdb
|
||||
end
|
||||
|
||||
def index_of(name)
|
||||
@head.index(name) || Rdb.die("no column named '#{name}'")
|
||||
Rdb.column_index(@head, name) || Rdb.die("no column named '#{name}'")
|
||||
end
|
||||
|
||||
def to_s
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rdb
|
||||
VERSION = '0.2.0'
|
||||
end
|
||||
@@ -113,6 +113,22 @@ Phone (111) 222-3333
|
||||
Name Chiquita Banana
|
||||
Phone 1234
|
||||
.DE
|
||||
.SH "Case of Column Names"
|
||||
.I "(Ruby re-creation only.)"
|
||||
Column names are case sensitive, as in the original
|
||||
.BR /rdb .
|
||||
If the environment variable
|
||||
.B RDB_ICASE
|
||||
is set to 1, upper and lower case are ignored when column names are looked up.
|
||||
An exact match always wins;
|
||||
if several columns differ only in case, the command stops with an
|
||||
.I ambiguous
|
||||
error.
|
||||
Names that are also
|
||||
.B awk
|
||||
reserved words or are followed by
|
||||
.B (
|
||||
are never taken for columns.
|
||||
.SH "See Also"
|
||||
.B
|
||||
project,
|
||||
|
||||
@@ -355,6 +355,22 @@ compute 'Value = sprintf ("%7.2f", ( Cost * Amount ))' < inventory
|
||||
.DE
|
||||
.PP
|
||||
These commands will produce the first table under Examples, above.
|
||||
.SH "Case of Column Names"
|
||||
.I "(Ruby re-creation only.)"
|
||||
Column names are case sensitive, as in the original
|
||||
.BR /rdb .
|
||||
If the environment variable
|
||||
.B RDB_ICASE
|
||||
is set to 1, upper and lower case are ignored when column names are looked up.
|
||||
An exact match always wins;
|
||||
if several columns differ only in case, the command stops with an
|
||||
.I ambiguous
|
||||
error.
|
||||
Names that are also
|
||||
.B awk
|
||||
reserved words or are followed by
|
||||
.B (
|
||||
are never taken for columns.
|
||||
.SH "See Also"
|
||||
COHERENT Lexicon:
|
||||
.B
|
||||
|
||||
@@ -393,6 +393,22 @@ before
|
||||
.BR chart .
|
||||
It says that the standard input file
|
||||
will be file 1 or on the left side of the table.
|
||||
.SH "Case of Column Names"
|
||||
.I "(Ruby re-creation only.)"
|
||||
Column names are case sensitive, as in the original
|
||||
.BR /rdb .
|
||||
If the environment variable
|
||||
.B RDB_ICASE
|
||||
is set to 1, upper and lower case are ignored when column names are looked up.
|
||||
An exact match always wins;
|
||||
if several columns differ only in case, the command stops with an
|
||||
.I ambiguous
|
||||
error.
|
||||
Names that are also
|
||||
.B awk
|
||||
reserved words or are followed by
|
||||
.B (
|
||||
are never taken for columns.
|
||||
.SH "See Also"
|
||||
.B
|
||||
column,
|
||||
|
||||
+16
@@ -10,6 +10,22 @@ prints on the standard output all commands in directory
|
||||
.BR $RDB/bin .
|
||||
This command is a shell script, which you should edit if its output
|
||||
is not to your liking.
|
||||
.SH "Case of Column Names"
|
||||
.I "(Ruby re-creation only.)"
|
||||
Column names are case sensitive, as in the original
|
||||
.BR /rdb .
|
||||
If the environment variable
|
||||
.B RDB_ICASE
|
||||
is set to 1, upper and lower case are ignored when column names are looked up.
|
||||
An exact match always wins;
|
||||
if several columns differ only in case, the command stops with an
|
||||
.I ambiguous
|
||||
error.
|
||||
Names that are also
|
||||
.B awk
|
||||
reserved words or are followed by
|
||||
.B (
|
||||
are never taken for columns.
|
||||
.SH "See Also"
|
||||
.B
|
||||
act,
|
||||
|
||||
@@ -112,6 +112,22 @@ State Costa Rica
|
||||
Zip 123456789
|
||||
Phone 1234
|
||||
.DE
|
||||
.SH "Case of Column Names"
|
||||
.I "(Ruby re-creation only.)"
|
||||
Column names are case sensitive, as in the original
|
||||
.BR /rdb .
|
||||
If the environment variable
|
||||
.B RDB_ICASE
|
||||
is set to 1, upper and lower case are ignored when column names are looked up.
|
||||
An exact match always wins;
|
||||
if several columns differ only in case, the command stops with an
|
||||
.I ambiguous
|
||||
error.
|
||||
Names that are also
|
||||
.B awk
|
||||
reserved words or are followed by
|
||||
.B (
|
||||
are never taken for columns.
|
||||
.SH "See Also"
|
||||
.B
|
||||
column
|
||||
|
||||
+16
@@ -175,6 +175,22 @@ and
|
||||
.BR compute ,
|
||||
read the COHERENT manual's tutorial on
|
||||
.BR awk .
|
||||
.SH "Case of Column Names"
|
||||
.I "(Ruby re-creation only.)"
|
||||
Column names are case sensitive, as in the original
|
||||
.BR /rdb .
|
||||
If the environment variable
|
||||
.B RDB_ICASE
|
||||
is set to 1, upper and lower case are ignored when column names are looked up.
|
||||
An exact match always wins;
|
||||
if several columns differ only in case, the command stops with an
|
||||
.I ambiguous
|
||||
error.
|
||||
Names that are also
|
||||
.B awk
|
||||
reserved words or are followed by
|
||||
.B (
|
||||
are never taken for columns.
|
||||
.SH "See Also"
|
||||
.B
|
||||
compute,
|
||||
|
||||
@@ -165,6 +165,22 @@ People are often confused by
|
||||
.B sort
|
||||
because it does not behave the way one would expect.
|
||||
Study its entry in the COHERENT Lexicon.
|
||||
.SH "Case of Column Names"
|
||||
.I "(Ruby re-creation only.)"
|
||||
Column names are case sensitive, as in the original
|
||||
.BR /rdb .
|
||||
If the environment variable
|
||||
.B RDB_ICASE
|
||||
is set to 1, upper and lower case are ignored when column names are looked up.
|
||||
An exact match always wins;
|
||||
if several columns differ only in case, the command stops with an
|
||||
.I ambiguous
|
||||
error.
|
||||
Names that are also
|
||||
.B awk
|
||||
reserved words or are followed by
|
||||
.B (
|
||||
are never taken for columns.
|
||||
.SH "See Also"
|
||||
COHERENT Lexicon:
|
||||
.B
|
||||
|
||||
@@ -133,6 +133,22 @@ that is easy to find, such as
|
||||
.PP
|
||||
If you only want to see the error lines, use the command
|
||||
.BR row .
|
||||
.SH "Case of Column Names"
|
||||
.I "(Ruby re-creation only.)"
|
||||
Column names are case sensitive, as in the original
|
||||
.BR /rdb .
|
||||
If the environment variable
|
||||
.B RDB_ICASE
|
||||
is set to 1, upper and lower case are ignored when column names are looked up.
|
||||
An exact match always wins;
|
||||
if several columns differ only in case, the command stops with an
|
||||
.I ambiguous
|
||||
error.
|
||||
Names that are also
|
||||
.B awk
|
||||
reserved words or are followed by
|
||||
.B (
|
||||
are never taken for columns.
|
||||
.SH "See Also"
|
||||
.SH "Logical Conditions"
|
||||
.B
|
||||
|
||||
@@ -478,4 +478,41 @@ class ActTest < RdbCase
|
||||
assert_includes out, 'Duplicate Short names in chart'
|
||||
assert st.success?, err
|
||||
end
|
||||
|
||||
ICASE = { 'RDB_ICASE' => '1' }.freeze
|
||||
|
||||
def test_column_names_are_case_sensitive_by_default
|
||||
_o, err, st = run3('column', 'cost', stdin: File.read(File.join(@dir, 'inventory')))
|
||||
assert st.success?, err
|
||||
assert_equal %w[cost], _o.lines.first.chomp.split("\t")
|
||||
end
|
||||
|
||||
def test_icase_column_and_sorttable
|
||||
out, err, st = run3('column', 'COST', 'item', stdin: File.read(File.join(@dir, 'inventory')), env: ICASE)
|
||||
assert st.success?, err
|
||||
assert_equal %w[Cost Item], out.lines.first.chomp.split("\t")
|
||||
out, = run3('sorttable', '-nr', 'cost', stdin: File.read(File.join(@dir, 'inventory')), env: ICASE)
|
||||
assert_equal 'scales', data(out).first.last
|
||||
end
|
||||
|
||||
def test_icase_compute_and_awk_words
|
||||
src = File.read(File.join(@dir, 'inventory'))
|
||||
out, err, st = run3('compute', 'value = sprintf("%5.2f", cost * AMOUNT)', stdin: src, env: ICASE)
|
||||
assert st.success?, err
|
||||
assert_equal '1312.75', data(out)[5][3].strip
|
||||
# a column called Length must not swallow the length() function
|
||||
t = "Length\tName\n------\t----\n3\tab\n"
|
||||
out, err, st = run3('compute', 'Length = length(name)', stdin: t, env: ICASE)
|
||||
assert st.success?, err
|
||||
assert_equal [%w[2 ab]], data(out)
|
||||
end
|
||||
|
||||
def test_icase_ambiguous_column_is_an_error
|
||||
t = "Name\tname\n----\t----\na\tb\n"
|
||||
_o, err, st = run3('column', 'NAME', stdin: t, env: ICASE)
|
||||
refute st.success?
|
||||
assert_match(/ambiguous/, err)
|
||||
out, = run3('column', 'name', stdin: t, env: ICASE)
|
||||
assert_equal [%w[b]], data(out)
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user