1 Commits
Author SHA1 Message Date
wkh 32c1f2be28 Version 0.3.0: testall.cmds mit einem Beispiel pro Befehl 2026-10-11 21:45:23 +02:00
9 changed files with 1277 additions and 76 deletions
+15
View File
@@ -7,6 +7,21 @@ folgen [Semantic Versioning](https://semver.org/lang/de/). Die Version steht in
## [Unveröffentlicht]
## [0.3.0] - 2026-10-11
### Hinzugefügt
- `demo/testall.cmds` enthält zu jedem der 134 Befehle ein ausführbares Beispiel,
gegliedert durch Überschriften (`##`-Zeilen werden mit ausgegeben, `#`-Zeilen
sind stille Kommentare). Ein Test prüft, dass kein Befehl fehlt.
### Geändert
- `testall` läuft in einer temporären Kopie von `demo/`; schreibende Beispiele
(`append`, `delete`, `close` ...) verändern die Demodaten nicht mehr. Die
Befehle lesen von einer leeren Standardeingabe.
- `sale` nimmt das Datum aus `NEXTDATE` (wie `getjournal`, `close`), sonst von heute.
- `lock` führt die Sperrdatei pro Pfad statt pro Dateiname, so stören sich
gleichnamige Tabellen in verschiedenen Ordnern nicht.
## [0.2.0] - 2026-10-11
### Hinzugefügt
+16 -1
View File
@@ -62,6 +62,21 @@ whatwill total Befehle, die zu einem Stichwort passen
commands Tabelle aller Befehle
```
## Beispiele zu allen Befehlen
`demo/testall.cmds` enthält zu jedem Befehl ein Beispiel mit kurzer Erklärung.
`testall` führt es in einer Kopie von `demo/` aus und zeigt jede Zeile samt Ausgabe:
```
testall | less alle Beispiele ansehen
testall > testall.old Lauf als Vergleich festhalten
testall | diff testall.old - später prüfen, was sich geändert hat (mise run testall)
```
Eine Zeile mit `##` erscheint als Überschrift in der Ausgabe, eine mit `#` ist
ein stiller Kommentar. Neue Befehle brauchen hier ein Beispiel, sonst schlägt der Test
`test_testall_mentions_every_command` fehl.
## Befehle
| Gruppe | Befehle |
@@ -138,7 +153,7 @@ lib/rdb/table.rb Tabellen und Listen lesen und schreiben
lib/rdb/fast.rb Schnellzugriff über Byte-Offsets
lib/rdb/cmd/*.rb die Befehle, nach Gruppen
man/ Man-Pages (troff-Quellen aus den MWC-Archiven)
demo/ Beispieltabellen, testall.cmds und testall.old
demo/ Beispieltabellen; testall.cmds = je ein Beispiel pro Befehl, testall.old = letzter Lauf
test/ Tests
```
+253 -8
View File
@@ -1,14 +1,259 @@
# One command per line; testall runs them in demo/ and prints the output.
# Save a known-good run: testall > testall.old ; later: testall | diff testall.old -
# testall.cmds -- one shell command per line; `testall` runs them in a scratch copy of demo/.
# ## line is printed as a heading/explanation # line is a silent comment
# Save a known-good run: testall > testall.old later: testall | diff testall.old -
# Tables used here: inventory journal expense carexpense parent female male owners
# chart ledger parttable cash X83 colors orders ed.t maillist form name
##
## ===== Selecting and arranging rows and columns =====
## row (select): keep the rows that match a condition. Column names work like awk variables.
row 'Cost > 5' < inventory
column Cost Amount Description < inventory
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
## 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)
addcol Total < inventory | compute 'Total = Amount * Cost' | column Item Total
compute 'Value = sprintf("%5.2f", Cost * Amount)' < inventory
total Amount Value < inventory
datatype -l < inventory
subtotal Date Debit Credit < journal
jointable parent female
## addcol / rename
addcol Supplier < owners
rename Owner Person < owners
## number / Number: number the rows (number: rows only, Number: rows and the head)
number < owners
Number < owners
## record: print one record (the 3rd row)
record 3 < inventory
## rmblank: drop empty rows; headon / headoff: add or strip the head
column Item Cost < inventory | rmblank
headoff < colors
headoff < colors | headon Hue
printf 'a\tb\n1\t2\n' | insertdash
## splittable: cut a table horizontally into pieces of n rows (writes colorsaa, colorsab ...)
splittable -3 colors ; ls colors* ; cat colorsab
## ===== Combining tables =====
## jointable: join on the first column; -a keeps unmatched rows, -j names the key columns
jointable inventory owners
jointable -a inventory owners
jointable -j1 Parent -j2 Name parent female
## union / intersect / difference
union expense carexpense
intersect female male
difference female male
## paste.rdb: put tables side by side
paste.rdb female male
## ===== Statistics: one result row per column =====
total Amount Value < inventory
mean Cost < inventory
maximum Cost < inventory
minimum Cost < inventory
width < inventory
precision Cost < inventory
datatype -l < inventory
widest < inventory
subtotal Date Debit Credit < journal
dbdict < inventory
schema inventory owners
tableorlist inventory maillist
## ===== Checking and displaying =====
check.rdb < inventory
printf 'a\tb\n-\t-\n1\t2\t3\n4\n' | check.rdb
display < owners
see < owners
validate 'Cost > 40 { print "expensive:", Description }' < badinventory
column Colors < orders | domain colors
domain colors black x
## fd: does Description depend on Item? (prints true/false)
fd Item Description < inventory
fd Description Cost < inventory
## ===== Text tools (arguments or standard input) =====
justify < inventory
tabletolist < inventory
justify -r Description < inventory
justify -c Cost < inventory
trim < inventory
compresss < inventory
pad -4 < owners
printf 'hello world\n' | cap
uppercase hello world
lowercase HELLO WORLD
printf 'one two\n' | uppercase
substitute gloves mittens colors ; cat colors
trimblank ' padded '
padstring 8 abc
padstring -8 abc
length 'how long?'
filesize inventory
ascii aA1
ascii ' '
chr 72 105
word < ed.t
cap < ed.t
blank < maillist
printf 'a\n' | prompt 'prompt text'
not false && echo 'false failed, so not succeeded'
path ls > /dev/null && echo 'ls found on the path'
## tabletom4 / tabletosed make translation files from ed.t; translate applies them
tabletom4 < ed.t
tabletosed < ed.t
tabletom4 < ed.t > ed ; echo 'I love you.' | translate ed
## uniondict builds the ed define-file out of ed.low and ed.allcap
printf 'English\tDeutsch\n-------\t-------\nlove\tliebe\n' > ed.low ; printf 'English\tDeutsch\n-------\t-------\nLOVE\tLIEBE\n' > ed.allcap ; uniondict ed
## ===== Conversions =====
tabletolist < owners
tabletolist -l < owners
tabletolist < owners | listtotable
listtotable < maillist
listtosh < maillist
tabletofix Item=4 Owner=6r < owners
tabletofix Item=4 Owner=6r < owners | fixtotable Item=1,4 Owner=5,6
tabletostruct tag owners < owners
tabletotbl < owners
tabletofact parent
tabletorule ruletable
## ===== Dates =====
## julian: dates to day numbers (arithmetic); gregorian: back again (-e European, -u US, -c day count)
julian Date < journal
julian Date < journal | gregorian Date
julian Date < journal | gregorian -e Date
## computedate: a date plus n days
computedate 890331 45
computedate 891231 1
test $(todaysdate | wc -c) -eq 7 && echo 'todaysdate prints YYMMDD'
## ===== Names that sound alike =====
soundex < name
## like: names that sound like Mann (needs the soundex table name.x, sorted by Soundex)
soundex < name > name.x ; like Mann name
hashkey 11 ABCD
hashkey 29 abcdefg
## ===== Fast access: index, search, seek =====
## search without an index scans the table (-ms); keys come from stdin
echo scales | search inventory Description
## -h leaves out the head line of the answer
printf 'Description\n-----------\nscales\n' | search -h inventory Description
## build indexes in each method: b binary, r record, i inverted, h hash
index -mr inventory ; ls inventory.r ; echo 3 | search -mr inventory
index -mi inventory Description ; ls inventory.i ; echo cl | search -mi -x inventory Description
index -mh inventory Item ; ls inventory.h ; echo 5 | search -mh inventory Item
## seek: where does the row start and end? (byte offsets)
echo 5 | seek inventory Item
echo 5 | seek -mh inventory Item
## searchtree: is Sally an ancestor of Jane? (needs a table of Parent, Child)
searchtree parent Sally Jane Parent Child
## vindex: look-up tables for ve
vindex owners Item : Owner ; vindex owners
tableorlist owners maillist
## ===== Changing tables: append, replace, delete, enter =====
## append adds a row and updates the indexes
## -h: the new row has no head line
printf '8\t1\t1.00\t1.00\tnew thing\n' | append -h -mh inventory Item ; echo 8 | search -mh inventory Item
## replace overwrites bytes from..to; take the offsets from seek
set -- $(echo 3 | seek inventory Item) ; printf '3\t6\t8.00\t48.00\tclamps\n' | replace inventory $1 $2 ; echo 3 | search inventory Item
## delete blanks the row and fixes the index
echo 4 | delete -mh inventory Item ; echo 4 | search -mh inventory Item ; echo 5 | search -mh inventory Item
## enter reads rows field by field from the terminal; -1 means one row
printf '9\n2\n3\n4\nnewer thing\n' | enter -1 inventory > /dev/null
tail -1 inventory
## lock / unlock: the second process may not lock the same bytes
lock inventory 111 10 20 0 0 ; echo "first lock status $?"
lock inventory 222 15 25 0 0 ; echo "second lock status $?"
unlock inventory 111 10 20 0 0 ; echo "unlock status $?"
lock inventory 222 15 25 0 0 ; echo "second lock now status $?" ; unlock inventory 222 15 25 0 0
## vilock edits a table under a lock; update.rdb edits records interactively
echo | vilock inventory ; echo "vilock status $?"
## update.rdb with a key on stdin and no changes (an empty line ends the dialog)
printf '3\n\n' | update.rdb inventory Item | head -8
## ve is a line editor in this version
printf 'q\n' | ve inventory | head -3
## ===== Reports and forms =====
## report fills a form with the fields of each list record (<Name>)
cat form
report form < maillist
## label prints mailing labels, letter prints form letters
label < maillist
letter form < maillist | head -5
## screen turns a form into a shell script that asks for the fields
screen < form | head -6
## reportwriter: a sample report with page headings (the date is cut out)
reportwriter -3 Prices < inventory | grep -v 'Report Date'
## tabletom4 / tabletosed / tabletostruct were shown above
## ===== Help and the terminal =====
rdb --version
howmany
whatis row
whatwill total
helpme
commands | head -5
man -g column | head -12
termput cl | cat -v
termput cm | cat -v
tset vt100 | head -3
cursor 3 8 | cat -v
clear.rdb | cat -v
rmcore ; echo "rmcore status $?"
mkdir t1 ; cp owners t1 ; cpdir.rdb t1 t2 ; ls t2
menu < /dev/null | head -3
timesearch > /dev/null ; echo "timesearch status $?"
testsearch | tail -3
act
## ===== /act: a small accounting system =====
## (the commands work on tables in the current directory; NEXTDATE fixes the date they use)
act
## cashflow: compute the Balance column of a cash table
cashflow < cash
## tax: look up the tax for an income in a tax table
tax 20000 < X83
## explode: parts list -> all subparts with counts (part, amount)
explode 10001 2 parttable
## chartdup: look for duplicate account numbers and short names in chart
printf 'Account\tShort\n-------\t-----\n1\ta\n1\tb\n2\ta\n' > chart ; chartdup
## foot: subtotals of Debit and Credit per account in the ledger
foot
## adjust, balance, close: need a trial balance and an adjusting journal
printf 'Account\tDebit\tCredit\tFormat\tTaxline\tShort\tName\n-------\t-----\t------\t------\t-------\t-----\t----\n1010\t11178\t\tB-A\t4L01\tcash\tCash\n3010\t\t13500\tI-I\t101a\tsales\tSales\n' > trialbalance
printf 'Account\tDebit\tCredit\tFormat\tTaxline\tName\n-------\t-----\t------\t------\t-------\t----\n1010\t100\t\tB-A\t4L01\tCash\n' > journaladjust
adjust ; cat adjustedtrial
balance ; sed 2d balancesheet
NEXTDATE=870101 close ; cat journalnext
## fillform: sum the adjusted trial balance per tax line into a tax form
printf 'Taxline\tDescription\n-------\t-----------\n4L01\tCash on hand\n101a\tGross sales\n' > taxform.f1
fillform f1 ; cat f1
## calculate: compute columns of a form; f1.calc has one 'Column = expression' per line
echo 'Amount = Amount * 2' > f1.calc ; calculate f1 ; cat f1
## getjournal / consolidate: collect subsidiary journals into the general journal
printf 'Date\tAmount\tAccount\tRef\tDescription\n----\t------\t-------\t---\t-----------\n870101\t100\t4010\ts1\tshop sale\n' > journalsales
NEXTDATE=870131 getjournal journalsales
printf 'Date\tAmount\tAccount\tRef\tDescription\n----\t------\t-------\t---\t-----------\n870102\t40\t5010\tp1\tpaper\n' > journalexpense
rm journal ; consolidate 870131 ; cat journal
## payroll: calcpay sorts journalpay into ledgerpay, w2 totals per employee
printf 'Number\tGross\tFederal\tState\tNet\n------\t-----\t-------\t-----\t---\n2\t1000\t100\t50\t850\n1\t900\t90\t40\t770\n2\t1000\t100\t50\t850\n' > journalpay
calcpay ; cat ledgerpay
w2
## ===== /act: sales and stock =====
printf 'Code\tName\tPrice\n----\t----\t-----\n10010\twidget\t2.50\n10020\tgadget\t10\n' > product
printf 'Number\tCompany\tLast\n------\t-------\t----\n7\tAcme\tRoe\n' > customer
## sale: enter a sale order (customer, then code and quantity per item, then y to confirm)
printf '7\n10010\n4\n10020\n1\n\ny\n\n' | NEXTDATE=870105 sale
cat salesorder
## invoice / cstate: print an invoice or customer statement for what was sold
printf '1\n\n' | invoice
printf '7\n\n' | cstate
## bom: parts needed for everything sold (needs the table part with Code, Subpart, Count)
printf 'Code\tSubpart\tCount\n----\t-------\t-----\n10010\ts1\t3\n10020\ts1\t1\n10020\ts2\t2\n' > part
bom ; cat bom
## update.inv: change the stock of one item, with record locking (INV names the table)
cp inventory inv ; printf '7\n9\n\n' | INV=inv update.inv | tail -n +2
grep '^7' inv
+963 -58
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -165,10 +165,12 @@ Rdb.register('enter', 'Add rows to a table or list file without an editor', 'ent
end
end
require 'digest'
# ---- locks -------------------------------------------------------------------
def lock_file(name)
File.join(Dir.tmpdir, "L#{File.basename(name)}")
File.join(Dir.tmpdir, "L#{File.basename(name)}.#{Digest::MD5.hexdigest(File.expand_path(name))[0, 8]}")
end
Rdb.register('lock', 'Lock a record or field of a file', 'lock filename processid from to indexfrom indexto') do |args|
+1 -1
View File
@@ -365,7 +365,7 @@ Rdb.register('sale', 'Enter sale order, and item, update customer', 'sale') do |
next if items.empty?
tax = gross * 0.05
order = [n.to_s, cust, Date.today.strftime('%y%m%d'), '0', format('%.2f', gross), format('%.2f', tax), format('%.2f', gross + tax)]
order = [n.to_s, cust, next_date, '0', format('%.2f', gross), format('%.2f', tax), format('%.2f', gross + tax)]
puts Rdb::Table.new(%w[Number Cust Date Shipped Gross Tax Total], [order]).to_s
if ask('Is this correct? (y, n) ').downcase.start_with?('y')
File.write('salesorder', Rdb::Table.open('salesorder').tap { |t| t.rows << order }.to_s)
+15 -3
View File
@@ -227,12 +227,24 @@ end
Rdb.register('testall', 'Test all /rdb programs in directory $RDB/demo', 'testall [ > testall.new ]') do |_args|
ENV['PATH'] = "#{File.join(Rdb::ROOT, 'bin')}:#{ENV['PATH']}"
demo = File.join(ENV['RDB'] || Rdb::ROOT, 'demo')
Dir.chdir(demo)
script = File.join(demo, 'testall.cmds')
File.exist?(script) or die "#{script}: missing"
File.readlines(script, chomp: true).reject { |l| l.strip.empty? || l.start_with?('#') }.each do |cmd|
# Run in a scratch copy: some commands (append, delete, close ...) change files.
Dir.mktmpdir('rdb-testall') do |dir|
FileUtils.cp_r(Dir[File.join(demo, '*')], dir)
Dir.chdir(dir)
File.readlines(script, chomp: true).each do |cmd|
next if cmd.strip.empty?
if cmd.start_with?('##') # '##' lines are shown, a single '#' is a silent comment
puts cmd
next
end
next if cmd.start_with?('#')
puts "$ #{cmd}"
$stdout.flush
puts `#{cmd} 2>&1`
puts Open3.capture2e('sh', '-c', cmd, stdin_data: '').first
end
end
end
+1 -1
View File
@@ -1,5 +1,5 @@
# frozen_string_literal: true
module Rdb
VERSION = '0.2.0'
VERSION = '0.3.0'
end
+7
View File
@@ -445,6 +445,13 @@ class HelpTest < RdbCase
assert_equal "\e[3;8H", rdb('cursor', '3', '8')
end
def test_testall_mentions_every_command
script = File.read(File.join(DEMO, 'testall.cmds'))
names = data(rdb('commands')).map(&:first)
missing = names.reject { |n| script.match?(/(^|[\s|;(])#{Regexp.escape(n)}(\s|$)/) }
assert_empty missing, "testall.cmds lacks an example for: #{missing.join(' ')}"
end
def test_testall_runs_demo_script
out = rdb('testall')
assert_includes out, '$ row'