summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md21
-rw-r--r--README.md23
-rw-r--r--cmd/krino/main.go2
-rw-r--r--docs/design.md20
-rw-r--r--man/krino.112
-rw-r--r--man/krino.conf.524
6 files changed, 80 insertions, 22 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f34c309..f86844f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,20 +22,28 @@ Hostile names and contents
same plan; it skips or takes a free name instead.
- A content exclude holds when its content test cannot read the file
(fails closed), so a protected file krino could not read is left alone.
+ A file whose format has no text (an image, an archive) simply does not
+ match a content test, so it is not set aside.
- Extraction tools are only found through absolute PATH entries.
Apply and undo
-- Every step is logged as soon as it has run; SIGHUP is handled like
- SIGTERM. A run killed mid-chain can be undone as far as it got.
+- Every step is logged as soon as it has run; a step that ran but could
+ not be logged is reported with where its file is now. SIGHUP is handled
+ like SIGTERM, unless krino runs under `nohup`. An interrupt finishes the
+ current step and skips the rest of that file's chain. A run killed
+ mid-chain can be undone as far as it got.
- Undo reverses a file its run renamed and then moved, moved twice, or
moved and then trashed (it refused them before).
- Undo restores a trash entry only while it is still the file the run put
there, re-checks every file just before acting, keeps same-named files
- of two directories apart, and leaves no directory behind.
+ of two directories apart, and leaves no directory behind once it
+ completes. Finishing an undo that stopped part way refuses a file
+ changed since.
- An undo that stopped part way can be finished by undoing the run again;
plain `krino undo` after an undo does that.
- The log restores a missing final newline, so a crash mid-line no longer
- breaks the next run's undo.
+ breaks the next run's undo, and a damaged line refuses only the file it
+ names.
- Trash entries skip names left orphaned in the Trash, and long names are
shortened to fit.
@@ -52,7 +60,10 @@ Command line
- `w` stops krino even when applying fails.
- `--min-age` is refused by commands it does not apply to, and an empty
value is an error; a file dated in the future counts as brand new.
-- `krino new` refuses a path that is not valid UTF-8, with that reason.
+- `krino new` refuses a path that is not valid UTF-8 or holds control or
+ bidirectional characters, with that reason.
+- `-n`, `-y`, `--json` and `-v` are refused by commands that do not use
+ them, instead of ignored: `krino -n new` no longer writes config.
- A rule name cannot start with "(".
- Builds need Go 1.25 or newer and use the Go 1.26.8 toolchain;
`golang.org/x/text` is v0.41.0 (GO-2026-5970, and through the toolchain
diff --git a/README.md b/README.md
index 64c8a72..c453726 100644
--- a/README.md
+++ b/README.md
@@ -5,12 +5,12 @@ 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.1 is the engine
+by file, logs every step, and can undo a run. Version 0.0.7 is the engine
and this command-line interface: per-directory rule files, placeholders in
-destinations and new names, and builds for Linux, FreeBSD and OpenBSD. It is
-not a GUI (that comes later), a watch mode, OCR, EXIF dates, macOS or
-Windows support, a content cache, a shell-command action, or a way to look
-inside archives.
+destinations and new names, a keyword cache that makes a rerun fast, and
+builds for Linux, FreeBSD and OpenBSD. It is not a GUI (that comes later), a
+watch mode, OCR, EXIF dates, macOS or Windows support, a shell-command
+action, or a way to look inside archives.
## Install
@@ -94,7 +94,7 @@ EOF
```
This one rule matches any file whose name contains "acme" and moves it into
-`Filed/Acme`. See what it would do, without changing anything:
+`Filed/Acme`. See what it would do, without moving anything:
```
$ krino -n demo
@@ -216,14 +216,15 @@ rule content only when a private pattern list is configured with
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 refresh the keyword
- cache in `~/.cache/krino`, which only makes the next run faster.
+ 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.
## Status
-**0.0.1, local only.** The module path is `krino`; `go install
-krino@v0.0.1` only works once a remote is chosen and the imports are
-renamed to match (`docs/design.md`, §18). Until then, build from a clone.
+**0.0.7, 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.
diff --git a/cmd/krino/main.go b/cmd/krino/main.go
index c787cbf..973711e 100644
--- a/cmd/krino/main.go
+++ b/cmd/krino/main.go
@@ -46,7 +46,7 @@ const usage = `usage: krino [-y | -n] [-v] [--json] [-c FILE] [NAME...]
Sort the files in the directories listed in krino.conf by their rules.
-y apply without asking
- -n dry run: show the plan, change nothing
+ -n dry run: show the plan, change no file
-v also list unmatched, ignored and busy files
--json with -n: print the plan as JSON
-c FILE use FILE instead of ~/.config/krino/krino.conf
diff --git a/docs/design.md b/docs/design.md
index 07728fc..28c7c80 100644
--- a/docs/design.md
+++ b/docs/design.md
@@ -433,6 +433,11 @@ while evaluating this rule. Tests of equal cost keep their written order, so
every such `name` test in it has at least N groups, and a rule that uses
`{N}` with no `name` test at all.
+Known limitation: a `name` test matches the folded name when `fold` is on
+(the default), and its captures are taken from that same text, so `{1}`
+loses diacritics: `(name "^(.+)-faktura")` on `Łódź-faktura.pdf` makes `{1}`
+`Lodz`. Set `(fold no)` in the rule to keep them.
+
### 7.4 Conflicts
When the target already exists:
@@ -661,7 +666,7 @@ Reversals, last step first within each file:
|---|---|---|
| move, rename | move `dst` back to `src` | `dst` missing or changed (size/mtime), or `src` now exists |
| copy | move `dst` to Trash | `dst` missing or changed |
-| trash | restore from Trash, remove the `.trashinfo` | the Trash entry is gone, or `src` now exists |
+| trash | restore from Trash, remove the `.trashinfo` | the Trash entry is gone, is not the file this run trashed (size/mtime), or its `.trashinfo` now records another original path; or `src` now exists |
| displace | restore the displaced target from Trash | as above |
| mkdir | remove the directory if empty | not empty |
| delete permanent | none; reported as not undoable | always |
@@ -672,6 +677,11 @@ not empty is the exception: it means another file still lives there, not that
the world changed under us, so that refusal is recorded and the rest of the
file's reversal proceeds. Undo runs are logged like any other run.
+Known limitation: a reversal recreates the directories it needs to move a
+file back and removes them again only when the file's reversal completes.
+An undo interrupted or failed after recreating one, and never finished for
+that file, leaves it behind, empty.
+
## 11. Command line
```
@@ -685,16 +695,18 @@ krino log [-n N] list recent runs
krino undo [RUN] reverse a run (default: the last one)
-y apply without asking
--n dry run: show the plan, change no file (the keyword cache may be refreshed)
+-n dry run: show the plan, change no file (krino's empty state directory
+ may be created, the keyword cache refreshed)
-v also list unmatched, ignored and busy files; full match reasons
---json with -n: the plan as JSON (format unstable before 1.0)
+--json with -n: the plan as JSON (format unstable before 1.0); invalid
+ UTF-8 in a name becomes U+FFFD
-c FILE use FILE instead of ~/.config/krino/krino.conf
--no-color never colour the output, as when NO_COLOR is set
-P, --no-pager print the plan straight out, never through the pager
--min-age D for this run, every directory's min-age is D (0, 30s, 1d);
sorting and explain only; a future modification time counts as age 0
-h, --help help
---version print "krino 0.0.1"
+--version print "krino VERSION"
```
Exit status: 0 success, including nothing to do and everything declined;
diff --git a/man/krino.1 b/man/krino.1
index e9d41da..ecbadc2 100644
--- a/man/krino.1
+++ b/man/krino.1
@@ -85,7 +85,8 @@ Cannot be combined with
.Fl n .
.It Fl n
Dry run: print the plan and exit without changing any file.
-It may refresh the keyword cache, which only makes the next run faster.
+It may create krino's empty state directory, and refresh the keyword cache,
+which only makes the next run faster.
.It Fl v
Also list files that were skipped as unmatched, ignored or busy, and show
the full reason a test matched or not.
@@ -460,6 +461,15 @@ and a copy in progress can leave a temporary
.Pa .krino-*
file and an empty directory behind.
.Pp
+An undo recreates the directories it needs to move a file back, and removes
+them again only when that file's reversal completes; one interrupted or
+failed after recreating a directory, and never finished for that file,
+leaves the directory behind, empty.
+.Pp
+.Fl -json
+prints invalid UTF-8 in a file name as U+FFFD, so two such names can look
+the same in the JSON plan.
+.Pp
Undo identifies a file by its directory and its path within it.
A file one directory's rules move into another included directory, which
sorts it again in the same run, is two files to undo; the first may then be
diff --git a/man/krino.conf.5 b/man/krino.conf.5
index 1fc113d..d86267d 100644
--- a/man/krino.conf.5
+++ b/man/krino.conf.5
@@ -658,6 +658,15 @@ or
.Ql } .
.El
.Pp
+With
+.Ic fold
+on, the default, a
+.Ic name
+test matches the folded name and its captures come from that text, so
+.Ic {1}
+loses diacritics
+.Pq see Sx KNOWN LIMITATIONS .
+.Pp
.Ar FMT
is a strftime subset:
.Ql %Y %m %d %H %M %S %j %% .
@@ -788,6 +797,21 @@ Duplicate conditions with different scopes do not share an original, so two
such rules can between them move every copy of a group aside; nothing is
deleted.
.Sh KNOWN LIMITATIONS
+Captures
+.Ic {1} No ... Ic {9}
+come from the folded name when
+.Ic fold
+is on:
+.Ql (name \(dq^(.+)-faktura\(dq)
+on
+.Pa \[/L]\['o]d\[u017A]-faktura.pdf
+makes
+.Ic {1}
+.Ql Lodz .
+Set
+.Ql (fold no)
+in the rule to keep diacritics.
+.Pp
A
.Ar dest
whose