aboutsummaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'README.md')
-rw-r--r--README.md328
1 files changed, 131 insertions, 197 deletions
diff --git a/README.md b/README.md
index ef7eca4..c7212fb 100644
--- a/README.md
+++ b/README.md
@@ -1,103 +1,112 @@
# krino
-krino sorts the files in one or more directories by rules: conditions on a
-file's type, name, path, size, age, text content or duplicate status,
-combined with `and` / `or` / `not`, that trigger actions — copy, move,
-rename, delete (to the Trash by default), chained in the order written. It
-shows the plan before touching anything, lets you approve all of it or file
-by file, logs every step, and can undo a run. Version 0.0.9 is the engine
-and this command-line interface: per-directory rule files, placeholders in
-destinations and new names, a keyword cache that makes a rerun fast, and
-builds for Linux, FreeBSD and OpenBSD. Version 0.0.11 has `krino-gui`, an
-optional GTK 4 window onto the same engine (see below). It is not a watch
-mode, OCR, EXIF dates, macOS or Windows support, a shell-command action, or
-a way to look inside archives.
+krino sorts a directory by rules you write. It reads the files, works out
+what belongs where, shows you the plan, and touches nothing until you say
+so. Every run is logged, and any run can be undone.
-## Install
+Rules are conditions — a file's type, name, path, size, age, text content,
+or whether it is a duplicate of something you already have — combined with
+`and`, `or` and `not`, and actions: move, copy, rename, delete. There is a
+command line and an optional GTK 4 window onto the same engine.
-```
-make && make install
-```
+![The plan, before anything has been touched](docs/krino-gui.png)
-`make` builds `./krino`; Go fetches the module dependencies itself, and the
-Go toolchain `go.mod` names (on OpenBSD, whose `go` package defaults to
-`GOTOOLCHAIN=local`, run `GOTOOLCHAIN=auto make`). `make
-install` puts the binary in `$PREFIX/bin` (default `~/.local/bin`), the man
-pages in `$PREFIX/share/man`, and the examples and `docs/sexp-primer.md` in
-`$PREFIX/share/doc/krino`.
+## A rules file
-A release tarball holds the built binary, the man pages, the examples and
-this README, but no Makefile and no source; install it by hand:
+One file per directory, in `~/.config/krino/dirs/`:
-```
-install -m 755 krino ~/.local/bin/
-install -m 644 krino.1 ~/.local/share/man/man1/
-install -m 644 krino.conf.5 ~/.local/share/man/man5/
-```
+```lisp
+(path "~/Downloads")
-Some `(content ...)` tests need external extractors — `pdftotext` for PDF,
-`antiword`/`catdoc` for legacy Word, `xls2csv`/`catppt` for legacy Excel and
-PowerPoint. `make build` reports which of these are missing; install them
-with:
+;; Never touch these, whatever the rules below say.
+(exclude (name "^keep-"))
-```
-make deps
+(rule "invoices"
+ (when (type document)
+ (content "invoice" "faktura"))
+ (move "Filed/Invoices/{mtime:%Y}")
+ (stop))
+
+(rule "screenshots"
+ (when (and (type image) (name "^screenshot-([0-9-]+)")))
+ (rename "screen {1}.png")
+ (move "Filed/Pictures")
+ (stop))
+
+(rule "copies I already have"
+ (when (duplicate "~/Documents"))
+ (move "~/.dupes")
+ (stop))
+
+(rule "old installers"
+ (when (and (type iso exe) (age > 90d)))
+ (delete))
```
-`make deps` is the only target that asks for privileges — it calls the
-system package manager (`apt-get`, `pkg`, or `pkg_add`, depending on the
-OS). Plain `make` never does.
+Conditions in a `(when ...)` must all hold; a `(content ...)` is true when
+any of its keywords appears. `{mtime:%Y}` and `{1}` are placeholders — the
+file's year, and the first capture group of the name test. `(stop)` means a
+file this rule matched takes no later rule. Four worked files are in
+`examples/`, and `krino.conf(5)` is the reference for every form.
-## The 60-second quickstart
+## What it can do
-Everything below happens in `/tmp/krino-demo` and a scratch config under
-`/tmp/krino-xdg`, so it never touches a real directory or your real krino
-config. Copy-paste it as written and your output should match what's pasted
-here, except for the run id and timestamps, which are different every time.
+- **Conditions:** `type` (by extension or by kind — `image`, `document`,
+ `archive`), `name` and `path` (regular expressions, with capture groups),
+ `size`, `age`, `content` (text pulled out of PDFs and office documents),
+ `duplicate` (the same bytes as a file somewhere else), and `matched`
+ (what an earlier rule decided). Combined with `and`, `or`, `not`.
+- **Actions,** chained in the order written: `move`, `copy`, `rename`,
+ `delete`. Placeholders fill in the file's name, its date, or a capture
+ group from the rule that matched it.
+- **A plan first.** `-n` prints what would happen and changes nothing.
+ Without it krino asks — all of it, or file by file, one key each.
+- **Undo.** `krino undo` reverses the last run, or one named by id. A
+ reversal the world has moved on from is refused for that file, not
+ guessed at.
+- **A log.** Every step of every run, tab-separated, in
+ `~/.local/state/krino/krino.log`.
+- **Fail-closed.** A PDF whose text cannot be read does not match a
+ content test, and so is left alone rather than filed by a guess.
+- **Fast on a rerun.** Extracted keywords are cached, so a second pass over
+ a large directory does not re-read every document.
-Create a handful of files of different types:
+It is not a watch mode, OCR, EXIF dates, a shell-command action, a way to
+look inside archives, or macOS and Windows support.
+
+## Install
```
-mkdir -p /tmp/krino-demo
-cd /tmp/krino-demo
-for f in acme-invoice.pdf photo.jpg podcast.mp3 archive.zip app.deb notes.txt; do
- printf 'demo file: %s\n' "$f" >"$f"
-done
+make && make install
```
-Point krino at a scratch config for this walkthrough:
+`make install` puts the binary in `$PREFIX/bin` (default `~/.local/bin`),
+the man pages in `$PREFIX/share/man`, and the examples and a short
+s-expression tutorial in `$PREFIX/share/doc/krino`. On OpenBSD, whose Go
+package defaults to `GOTOOLCHAIN=local`, run `GOTOOLCHAIN=auto make`.
-```
+Some `(content ...)` tests need external extractors — `pdftotext` for PDF,
+`antiword` or `catdoc` for legacy Word, `xls2csv` and `catppt` for legacy
+Excel and PowerPoint. `make build` says which are missing, and `make deps`
+installs them with the system package manager. That is the only target that
+asks for privileges; plain `make` never does.
+
+## Try it in a scratch directory
+
+```sh
+mkdir -p /tmp/krino-demo && cd /tmp/krino-demo
+for f in acme-invoice.pdf photo.jpg podcast.mp3 notes.txt; do
+ printf 'demo file: %s\n' "$f" >"$f"
+done
export XDG_CONFIG_HOME=/tmp/krino-xdg/config
export XDG_STATE_HOME=/tmp/krino-xdg/state
export XDG_DATA_HOME=/tmp/krino-xdg/data
-```
-
-Set it up:
-
-```
-$ krino init
-created /tmp/krino-xdg/config/krino/krino.conf
-created /tmp/krino-xdg/config/krino/template.conf
-next: krino new NAME PATH, for example: krino new downloads ~/Downloads
-$ krino new demo /tmp/krino-demo
-created /tmp/krino-xdg/config/krino/dirs/demo.conf and added "demo" to include
-edit its rules, then check them with: krino check demo
-```
-`krino new` copied the template to `dirs/demo.conf` and filled in the path.
-Replace its body with a small rule. The built-in `min-age` is 2 minutes
-(files skip as "too new" while they might still be downloading), which the
-demo files we just created are younger than, so this also turns it down to
-0 for this directory (`--min-age 0` would do the same for a single run):
-
-```
+krino init
+krino new demo /tmp/krino-demo
cat > "$XDG_CONFIG_HOME/krino/dirs/demo.conf" <<'EOF'
-;; -*- mode: lisp -*-
-;; vim: set ft=lisp :
-
(path "/tmp/krino-demo")
-(min-age 0s) ; demo files are seconds old; built-in is 2m
+(min-age 0s) ; these files are seconds old; the built-in is 2m
(rule "acme"
(when (name "acme"))
@@ -105,84 +114,33 @@ cat > "$XDG_CONFIG_HOME/krino/dirs/demo.conf" <<'EOF'
EOF
```
-This one rule matches any file whose name contains "acme" and moves it into
-`Filed/Acme`. See what it would do, without moving anything:
+`krino -n demo` prints the plan and changes nothing:
```
-$ krino -n demo
krino: demo /tmp/krino-demo
-6 scanned · 1 to act on · 0 warnings · 0.00s
+4 scanned · 1 to act on · 0 warnings · 0.00s
1 acme-invoice.pdf
move → Filed/Acme/
rule acme
because name "acme"
-not acted on: 5 unmatched (-v lists them)
+not acted on: 3 unmatched (-v lists them)
```
-On a terminal each block is wrapped to its width. A plan taller than the
-terminal opens in your pager; `-P` prints it straight out instead, and
-`krino -n > plan.txt` saves it as plain text, one field per line.
-
-Without `-n` or `-y`, `krino demo` shows the same plan and then asks, one
-key at a time, no Enter needed:
+Without `-n` krino shows that plan and asks — `[a] apply all`, `[c] choose
+per file`, `[s] skip this directory`, `[q] quit` — and under `c`, each file
+in turn, where `t` sends it to the Trash and `d` deletes it instead of what
+the rules planned. `-y` applies without asking, which is what a README can
+honestly paste:
```
-[a] apply all [c] choose per file [s] skip this directory [q] quit
-```
-
-`c` asks about each file in turn, showing the same block as the plan above:
-
-```
-[1/1] acme-invoice.pdf
- move → Filed/Acme/
- rule acme
- because name "acme"
- [y] yes [n] no [a] yes to this and all remaining [t] trash [d] delete permanently [w] write, apply chosen so far [q] quit, apply nothing
-```
-
-Approval is per file: a file's whole chain runs, or none of it. `t` sends
-the file to the Trash and `d` deletes it permanently (after a `y`) instead
-of what the rules planned. Each choice is echoed in red. `w` applies what you
-chose so far and quits, so a long review can be finished another day. A README
-can't paste a session it didn't actually run in a terminal, so here we
-apply directly with `-y`, which shows the same plan and applies it without
-asking:
-
-```
-$ krino -y demo
-krino: demo /tmp/krino-demo
-6 scanned · 1 to act on · 0 warnings · 0.00s
-
- 1 acme-invoice.pdf
- move → Filed/Acme/
- rule acme
- because name "acme"
-
-not acted on: 5 unmatched (-v lists them)
+$ krino -y demo # the plan above, and then:
1 applied · 0 failed · 0 declined
-```
-
-`acme-invoice.pdf` is now in `Filed/Acme/`. Every run is logged:
-
-```
$ krino log
-20260913T005509-db14 2026-09-13 00:55 demo 1 moved
-```
-
-And every run can be undone — `undo` alone would show its own plan and ask
-the same way, with a menu of its own:
-
-```
-[a] apply all [c] choose per file [s] skip [q] quit
-```
-
-`-y` applies it directly:
-
-```
+20260917T115059-6df2 2026-09-17 11:50 demo 1 moved
$ krino undo -y
-krino: undo 20260913T005509-db14
+krino: undo 20260917T115059-6df2
1 files · 1 to reverse · 0 refused
# file steps
@@ -192,82 +150,58 @@ krino: undo 20260913T005509-db14
1 applied · 0 failed · 0 declined
```
-`/tmp/krino-demo` is back exactly as the first `printf` loop left it — that
-is what `krino undo` is for. Delete both scratch directories when you're
-done: `rm -rf /tmp/krino-demo /tmp/krino-xdg`.
-
-## Configuration
-
-Rules are s-expressions. `docs/sexp-primer.md` is a short tutorial on the
-syntax with no Lisp background assumed; `krino.conf(5)` (`man/krino.conf.5`
-in the source, installed as a man page) is the reference for every form,
-setting and condition. `examples/` has four worked rule files: by type, by
-content (invoices), by age (old installers), and renaming (screenshots).
-
-Rules live in `$XDG_CONFIG_HOME/krino` (default `~/.config/krino`) —
-**never in this repository**. A leak check guards that: it runs in `make ci`,
-and on every commit only after `make install-hooks`. It refuses staged files
-that contain your home directory's path or an email address, and it catches
-rule content only when a private pattern list is configured with
-`git config krino.leakpatterns FILE`.
+`/tmp/krino-demo` is back as it was. Clean up with
+`rm -rf /tmp/krino-demo /tmp/krino-xdg`.
## The window
-`krino-gui` is an optional GTK 4 window onto the same engine: it plans a
-directory, shows what would happen to each file with the explanation beside
-it, applies the files you check, undoes a run, and edits a directory's rules
-as forms or as text with the configuration checked as you type. Every
-decision is the engine's, so the window and the command line agree, and a
-run it applies is an ordinary run in the log that `krino undo` can reverse.
-
```
make gui && make install-gui
```
-It lives in `gui/`, a nested module of its own, and needs GTK 4 development
-files and cgo — which the command line does not, so neither is needed to
-build or use `krino` itself:
+`krino-gui` plans a directory, shows what would happen to each file with
+the reason beside it and the file itself below, applies the ones you check,
+undoes a run, and edits a directory's rules as forms or as text with the
+configuration checked as you type. Every decision is the engine's, so the
+window and the command line agree, and a run it applies is an ordinary run
+that `krino undo` can reverse.
+
+It lives in `gui/`, a nested module, and needs GTK 4 development files and
+cgo — which the command line does not, so neither is needed to build or use
+`krino` itself:
```
sudo apt install libgtk-4-dev libgirepository1.0-dev # Debian, Devuan
```
-The release tarballs hold the cross-compiled `krino` only; `krino-gui` is
-built from source on the machine that runs it. `man/krino-gui.1` describes
-the window, and `docs/gui-checklist.md` is what is run by hand before a
-release that changes it.
-
## Safety
-- `(delete)` moves a file to the freedesktop.org Trash by default, not
- `unlink(2)` — recoverable, by hand or with `krino undo`. `(delete
- permanent)` is the explicit opt-out, and undo can never reverse it.
-- Duplicates are found, never deleted. A rule can combine `(duplicate)` with
- `move`, never with `delete`, and no other rule can delete a file krino has
- found to be a duplicate. Move duplicates aside, for example to `~/.dupes/`,
- and delete them later yourself or with a tool such as jdupes.
-- Every step of every run is appended to `$XDG_STATE_HOME/krino/krino.log`
- (default `~/.local/state/krino/krino.log`), tab-separated and
- `grep`-able.
-- `krino undo` reverses the most recent run, or a specific one by id from
- `krino log`. A reversal that the world has moved on since (the target is
- gone, changed, or the original path is occupied again) is refused for
- that file rather than guessed at.
-- `-n` prints the plan and changes none of your files — no config is
- written, nothing is moved, nothing is logged. It may create krino's empty
- state directory and refresh the keyword cache in `~/.cache/krino`, which
- only makes the next run faster.
+- `(delete)` moves a file to the freedesktop.org Trash, not `unlink(2)`.
+ `(delete permanent)` is the explicit opt-out, and undo can never reverse
+ it.
+- Duplicates are found, never deleted. A rule may combine `(duplicate)`
+ with `move` but never with `delete`, and no other rule may delete a file
+ krino has found to be a duplicate. Move copies aside and delete them
+ yourself, or with a tool such as jdupes.
+- A directory is locked while krino works on it, so two runs cannot fight
+ over the same files.
+- Your rules live in `~/.config/krino`, never in this repository. A leak
+ check in `make ci` refuses staged files holding a home directory path or
+ an email address, and, with `git config krino.leakpatterns FILE`, any
+ pattern of your own.
## Status
-**0.0.11, local only.** The module path is `krino`; `go install` only
-works once a remote is chosen and the imports are renamed to match
-(`docs/design.md`, §18). Until then, build from a clone.
-`krino -n --json`'s output shape is unstable before krino 1.0 — don't
-script against it yet.
+**0.0.11.** The module path is `krino`; `go install` will work once the
+repository has a public URL and the imports are renamed to match. Until
+then, build from a clone. The shape of `krino -n --json` is unstable before
+1.0 — don't script against it yet.
+
+Builds and tests are run on Linux, FreeBSD and OpenBSD. The release
+tarballs hold the cross-compiled command line only; the window is built on
+the machine that runs it.
-## License
+## Licence
-Everything in this repository — code, man pages, examples and the embedded
-templates — is GPL-3.0-or-later. Full text in `LICENSE`. Go files, shell
-scripts and man pages carry an SPDX identifier.
+GPL-3.0-or-later, in full in `LICENSE`. By Lukasz Kasprzak —
+contact@labunix.xyz.