-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpta
More file actions
executable file
·248 lines (231 loc) · 7.88 KB
/
Copy pathpta
File metadata and controls
executable file
·248 lines (231 loc) · 7.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
#!/bin/sh
# pta - plain text accounting dispatcher
#
# Usage:
# pta <command> [patterns...] [options...]
# cat file | pta <command>
#
# When no input is piped, reads *.txt from the script directory automatically.
DIR="$(cd "$(dirname "$0")" && pwd)"
export PTA_DIR="$DIR"
# --- pta-tool discovery and shebang routing ---
# A data file may declare a converter on line 1:
# #!pta-tool: convert-csv --date 1 --amount 2
# pta reads (not executes) the shebang and invokes the named tool.
# Lookup order: ./pta-tools/ → $PTA_DIR/pta-tools/ → pta-tool-NAME on $PATH.
find_pta_tool() {
[ -x "./pta-tools/$1" ] && { echo "./pta-tools/$1"; return 0; }
[ -x "$PTA_DIR/pta-tools/$1" ] && { echo "$PTA_DIR/pta-tools/$1"; return 0; }
command -v "pta-tool-$1" 2>/dev/null && return 0
return 1
}
# Read one file; if line 1 is a pta-tool shebang, route through the tool.
# Tools read the file body on stdin, emit pta transactions on stdout.
emit_file() {
_f="$1"
_first=$(head -1 "$_f" 2>/dev/null | tr -d '\r')
case "$_first" in
'#!'*pta-tool*)
# Extract everything after the first 'pta-tool' token
_rest=${_first#*pta-tool}
_rest=${_rest#:}
# Word-split: first word = tool name, rest = args
set -- $_rest
_name="$1"; shift
_args="$*"
[ -z "$_name" ] && { echo "pta: empty tool name in shebang ($_f)" >&2; return 1; }
_path=$(find_pta_tool "$_name") || {
echo "pta: unknown pta-tool '$_name' (in $_f)" >&2
return 1
}
"$_path" $_args < "$_f"
;;
*)
cat "$_f"
;;
esac
}
# Emit all input files (or stdin if none), routing shebanged files through tools.
emit_files() {
if [ -n "$files" ]; then
for _f in $files; do emit_file "$_f" || return 1; done
else
cat
fi
}
usage() {
cat <<'EOF'
Usage: pta <command> [patterns...] [options]
cat file | pta <command>
Commands:
balance (bal) Aggregated account balances
register (reg) Postings with running balance
accounts List all accounts
by-month Monthly totals
by-month-account Monthly totals per account
tags Tag usage counts
stats Summary statistics
check Validate transactions balance to zero
print Reprint raw transactions
sort Sort postings chronologically
txns Reconstruct transactions from postings
runbal Running balance (minimal format)
invoice (inv) Render an invoice as HTML (tag postings with inv:N)
add Interactive transaction entry
tools List available pta-tools
Data files may declare a converter via shebang:
#!pta-tool: convert-csv --date 1 --amount 2
See examples/ for working demos.
Options:
--from DATE Start date (inclusive)
--to DATE End date (inclusive)
--period YM Month filter (e.g. 2024-03)
--year YYYY Year filter
--depth N Collapse accounts to depth N
--actual Exclude periodic/planned transactions
--planned Only periodic/planned transactions
-f FILE Input file (default: auto-detect *.txt)
Examples:
pta balance All-time balance
pta balance expenses --year 2024 Expenses for 2024
pta reg --period 2024-03 March register
pta balance --actual Real transactions only
pta balance --planned Forecast only
pta check Validate all transactions
pta accounts List all accounts
EOF
}
# --- Parse arguments ---
cmd=""
PTA_FROM="" PTA_TO="" PTA_PERIOD="" PTA_YEAR="" PTA_DEPTH=""
tag_filter=""
files=""
patterns=""
while [ $# -gt 0 ]; do
case "$1" in
--from|--after) PTA_FROM="$2"; shift 2 ;;
--to|--before) PTA_TO="$2"; shift 2 ;;
--period) PTA_PERIOD="$2"; shift 2 ;;
--year) PTA_YEAR="$2"; shift 2 ;;
--depth) PTA_DEPTH="$2"; shift 2 ;;
--actual) tag_filter="actual"; shift ;;
--planned) tag_filter="planned"; shift ;;
-f|--file) files="$files $2"; shift 2 ;;
-h|--help) usage; exit 0 ;;
bal|balance|reg|register|runbal|accounts|by-month|\
by-month-account|tags|stats|check|print|sort|sort-txns|txns|invoice|inv|add|tools|help)
cmd="$1"; shift ;;
-*) echo "pta: unknown option '$1'" >&2; exit 1 ;;
*)
# Positional: existing file → input, else account pattern
if [ -f "$1" ]; then
files="${files:+$files }$1"
else
patterns="${patterns:+$patterns }$1"
fi
shift ;;
esac
done
[ -z "$cmd" ] && {
if [ -n "$files" ]; then
cmd="print" # pta FILE → show converted/raw transactions
else
usage; exit 1
fi
}
[ "$cmd" = "help" ] && { usage; exit 0; }
export PTA_FROM PTA_TO PTA_PERIOD PTA_YEAR PTA_DEPTH
# --- Build account filter regex (space-separated patterns → alternation) ---
acct_regex=""
if [ -n "$patterns" ]; then
acct_regex=$(printf '%s' "$patterns" | tr ' ' '|')
fi
# --- Auto-load data files when stdin is a terminal ---
if [ -t 0 ]; then
if [ -z "$files" ]; then
files=$(ls *.txt 2>/dev/null | grep -v test || true)
[ -z "$files" ] && { echo "pta: no input (pipe data or create *.txt files)" >&2; exit 1; }
fi
fi
# --- Commands that read raw input (no postings preprocessing) ---
case "$cmd" in
tools)
_found=0
_seen=""
for _loc in "$PTA_DIR/pta-tools" "./pta-tools"; do
[ -d "$_loc" ] || continue
_canon=$(cd "$_loc" && pwd)
case " $_seen " in *" $_canon "*) continue ;; esac
_seen="$_seen $_canon"
for _t in "$_loc"/*; do
[ -f "$_t" ] && [ -x "$_t" ] && {
printf " %-16s (%s)\n" "$(basename "$_t")" "$_loc"
_found=1
}
done
done
[ "$_found" -eq 0 ] && echo " (none found — see examples/)"
exit 0
;;
check)
emit_files | "$DIR/check"
exit $?
;;
print)
emit_files
exit $?
;;
sort-txns)
emit_files | "$DIR/sort-txns"
exit $?
;;
add)
"$DIR/add"
exit $?
;;
invoice|inv)
# invoice number comes from positionals; go straight through postings
# (skip date/account filters - the inv:N tag is the selector)
emit_files | "$DIR/postings" | "$DIR/invoice" $patterns
exit $?
;;
esac
# --- Commands that go through the postings pipeline ---
# Pipeline: input → postings → date-filter → acct-filter → tag-filter → command
emit_files | "$DIR/postings" | {
# Date filter stage
if [ -n "$PTA_FROM" ] || [ -n "$PTA_TO" ] || [ -n "$PTA_PERIOD" ] || [ -n "$PTA_YEAR" ]; then
"$DIR/date-filter"
else
cat
fi
} | {
# Account filter stage
if [ -n "$acct_regex" ]; then
awk -v p="$acct_regex" '$3 ~ p'
else
cat
fi
} | {
# Tag filter stage (planned vs actual)
case "$tag_filter" in
actual) grep -v planned ;;
planned) grep planned ;;
*) cat ;;
esac
} | {
# Command stage
case "$cmd" in
bal|balance) "$DIR/balance" ;;
reg|register) "$DIR/register" ;;
runbal) "$DIR/runbal" ;;
accounts) "$DIR/accounts" ;;
by-month) "$DIR/by-month" ;;
by-month-account) "$DIR/by-month-account" ;;
tags) "$DIR/tags" ;;
stats) "$DIR/stats" ;;
sort) "$DIR/sort" ;;
txns) "$DIR/sort" | "$DIR/txns" ;;
*) echo "pta: unknown command '$cmd'" >&2; exit 1 ;;
esac
}