Version 0.3.0: testall.cmds mit einem Beispiel pro Befehl
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
+964
-59
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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)
|
||||
|
||||
+17
-5
@@ -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|
|
||||
puts "$ #{cmd}"
|
||||
$stdout.flush
|
||||
puts `#{cmd} 2>&1`
|
||||
# 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 Open3.capture2e('sh', '-c', cmd, stdin_data: '').first
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rdb
|
||||
VERSION = '0.2.0'
|
||||
VERSION = '0.3.0'
|
||||
end
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user