diff --git a/CHANGELOG.md b/CHANGELOG.md index 7218b95..25680d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ folgen [Semantic Versioning](https://semver.org/lang/de/). Die Version steht in ## [0.6.0] - 2026-10-11 ### Hinzugefügt +- `column -d Spalte ...` (und `project -d`) löscht Spalten: alle anderen bleiben in ihrer Reihenfolge. Fehler bei unbekannter Spalte oder wenn alle gelöscht würden. Dazu Abschnitt in `man/column.man`, Beispiel in `testall.cmds` und Tests. - Steuertabellen 2026 für `tax`: `demo/taxDE2026`, `taxAT2026`, `taxCH2026` (nur Bundessteuer), `taxLI2026` (nur Landessteuer), erzeugt mit `tools/gen_tax_tables.rb`, mit Tests. ### Geändert diff --git a/README.md b/README.md index 7561e2a..180b910 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,8 @@ Dasselbe gilt für **Listen** (ein Datensatz als `SpalteWert`-Zeilen, die Datei beginnt mit einer Leerzeile). `tabletolist` und `listtotable` wandeln um, die meisten Befehle lesen beides. +`column -d Spalte ...` löscht Spalten (alle anderen bleiben), `column Spalte ...` wählt sie aus. + `row`, `compute` und `validate` übergeben ihren Ausdruck an awk, nachdem sie Spaltennamen durch `$N` ersetzt haben. Es gilt also awk-Syntax. diff --git a/demo/testall.cmds b/demo/testall.cmds index 5d653e4..fad1f19 100644 --- a/demo/testall.cmds +++ b/demo/testall.cmds @@ -12,6 +12,9 @@ rdb select 'Description ~ /gloves/' < inventory ## column (project): pick columns in any order; a name that does not exist becomes a new empty column column Description Cost < inventory project Item Notes < owners +## column -d: keep all columns except the named ones (a deletion) +column -d Cost Value < inventory +subtotal Date Debit Credit < journal | column -d Account Description ## sorttable: -n numeric, -r reverse, -f fold case sorttable -nr Cost < inventory ## compute: calculate columns from other columns (the new column must exist, so make it with addcol) diff --git a/demo/testall.old b/demo/testall.old index 84296f3..c032c61 100644 --- a/demo/testall.old +++ b/demo/testall.old @@ -28,6 +28,23 @@ Item Notes 1 3 6 +## column -d: keep all columns except the named ones (a deletion) +$ column -d Cost Value < inventory +Item Amount Description +---- ------ ----------- +1 3 rubber gloves +2 100 test tubes +3 5 clamps +4 23 plates +5 99 cleaning cloth +6 89 bunsen burners +7 5 scales +$ subtotal Date Debit Credit < journal | column -d Account Description +Date Debit Credit +---- ----- ------ +861222 25000 25000 +861223 10000 10000 +861224 30000 30000 ## sorttable: -n numeric, -r reverse, -f fold case $ sorttable -nr Cost < inventory Item Amount Cost Value Description diff --git a/lib/rdb/cmd/relational.rb b/lib/rdb/cmd/relational.rb index 1e32988..28171ac 100644 --- a/lib/rdb/cmd/relational.rb +++ b/lib/rdb/cmd/relational.rb @@ -11,9 +11,20 @@ Rdb.register('row', 'Make a new table where rows match logical condition', end Rdb.alias_for('select', 'row') -Rdb.register('column', 'Display columns of a table in any order', 'column [ Column ... ] < tableorlist') do |args| +Rdb.register('column', 'Display columns of a table in any order', 'column [ -d ] [ Column ... ] < tableorlist') do |args| + drop = args.first == '-d' + args.shift if drop t = table_in - next t.write if args.empty? + next t.write if args.empty? && !drop + die 'usage: column -d Column ... < tableorlist' if drop && args.empty? + + if drop # keep every column except the named ones + gone = args.map { |a| t.index_of(a) } + keep = t.head.each_index.reject { |i| gone.include?(i) } + die 'column -d would delete every column' if keep.empty? + + next t.with(head: keep.map { |i| t.head[i] }, rows: t.rows.map { |r| keep.map { |i| r[i] } }).write + end 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 } diff --git a/man/column.man b/man/column.man index 2cac9e6..003baa9 100644 --- a/man/column.man +++ b/man/column.man @@ -129,6 +129,24 @@ Names that are also reserved words or are followed by .B ( are never taken for columns. +.SH "Deleting Columns" +.I "(Ruby re-creation only.)" +.DM +column -d Column ... < tableorlist +.DE +.PP +With the option +.BR -d , +.B column +keeps every column +.I except +those named. +The order of the other columns stays as it is. +It is an error to name a column that does not exist, +or to delete every column. +.DM +subtotal Date Debit Credit < journal | column -d Account Description +.DE .SH "See Also" .B project, diff --git a/test/rdb_test.rb b/test/rdb_test.rb index 4ef46f3..cc665e3 100644 --- a/test/rdb_test.rb +++ b/test/rdb_test.rb @@ -5,6 +5,31 @@ require 'pty' require_relative '../lib/rdb' class SelectionTest < RdbCase + def test_column_d_deletes_columns + out = rdb('column', '-d', 'Cost', 'Value', file: 'inventory') + assert_equal %w[Item Amount Description], out.lines.first.chomp.split("\t") + assert_equal %w[1 3 rubber\ gloves], data(out).first + assert_equal %w[Date Debit Credit], rdb('column', '-d', 'Account', 'Description', stdin: rdb('subtotal', 'Date', 'Debit', 'Credit', file: 'journal')).lines.first.chomp.split("\t") + end + + def test_column_d_errors + src = File.read(File.join(@dir, 'owners')) + [%w[-d], %w[-d Nope], %w[-d Item Owner]].each do |args| + out, err, st = run3('column', *args, stdin: src) + refute st.success?, "column #{args.join(' ')} should fail" + assert_equal '', out + refute_empty err + end + end + + def test_column_d_works_on_lists_and_with_icase + list = rdb('tabletolist', file: 'owners') + assert_includes rdb('column', '-d', 'Item', stdin: list), "Owner\tann" + out, _e, st = run3('column', '-d', 'COST', stdin: File.read(File.join(@dir, 'inventory')), env: { 'RDB_ICASE' => '1' }) + assert st.success? + refute_includes out.lines.first, 'Cost' + end + def test_splittable_pieces_and_errors out, = run3('splittable', '-4', 'inventory') assert_equal 4, data(File.read(File.join(@dir, 'inventoryaa'))).size @@ -463,7 +488,7 @@ end class HelpTest < RdbCase def test_whatis_whatwill_commands - assert_includes rdb('whatis', 'column'), " column - display columns of a table in any order\n column column [ Column ... ] < tableorlist\n" + assert_includes rdb('whatis', 'column'), " column - display columns of a table in any order\n column column [ -d ] [ Column ... ] < tableorlist\n" assert_includes rdb('whatwill', 'total'), 'subtotal' assert_operator data(rdb('commands')).size, :>, 120 assert_operator rdb('howmany').to_i, :>, 120