.\" SPDX-License-Identifier: GPL-3.0-or-later .Dd September 14, 2026 .Dt KRINO.CONF 5 .Os .Sh NAME .Nm krino.conf .Nd krino's configuration language and files .Sh DESCRIPTION .Xr krino 1 Ns 's configuration is written in s-expressions: each form is a list inside parentheses, holding an operation optionally followed by its arguments. A short tutorial is .Pa docs/sexp-primer.md in the source repository, installed at .Pa $PREFIX/share/doc/krino/sexp-primer.md . This page is the reference for every form. .Pp .Bl -bullet -compact .It .Sy List : .Ql \&( items separated by whitespace .Ql \&) . .It .Sy String : .Ql \&"...\&" . A backslash escapes only .Ql \&" and .Ql \e ; any other backslash is kept literally, so .Ql \&"\ebacme\eb\&" is the regex .Ql \ebacme\eb . Strings may span lines. .It .Sy Symbol : any other run of characters except whitespace, .Ql \&( , .Ql \&) , .Ql \&" , .Ql \&; . .It .Sy Comment : .Ql \&; to end of line. .El .Pp Encoding is UTF-8 only. There is no other syntax: no quote characters, no dotted pairs, no block comments. Paths, keywords, regexes and rule names must be strings; settings values, type names, operators, sizes and durations are symbols. .Pp Every list and atom's byte offset, line and column is recorded, so .Ic krino new can splice a name into .Ic include without disturbing anything else in the file, and a syntax error is reported as .Ar file : Ns Ar line : Ns Ar col . .Sh FILES .Ss krino.conf The main file, read first: .Bd -literal -offset indent (include "downloads" "invoices") ; dirs/\*(Ltname\*(Gt.conf, run in this order (log "~/.local/state/krino/krino.log") ; optional (defaults ; optional; any setting below (min-age 5m)) (exclude (type iso)) ; optional, may repeat .Ed .Bl -tag -width Ds .It Ic (include Ar name No ...) The directories to sort, in the order given. Each .Ar name has its rules in .Pa dirs/ Ns Ar name Ns Pa .conf . .It Ic (log Ar path ) Where the log goes. Optional; defaults to .Pa $XDG_STATE_HOME/krino/krino.log . .It Ic (defaults Ar setting No ...) Defaults for every directory; a directory's own file, and a rule inside it, can override them. See .Sx SETTINGS . .It Ic (exclude Ar condition No ...) May repeat. Sets matching files aside in every directory; see .Sx EXCLUSIONS . .El .Ss dirs/name.conf One file per configured directory: .Bd -literal -offset indent (path "~/downloads") ; required (recursive no) ; any setting from SETTINGS (ignore "*.part" "*.aria2" ".*") ; may repeat; patterns accumulate in order (exclude (name "^keep-")) ; may repeat (rule "acme" (when (type document) (or (content "acme ltd" "0000000000") (name "(^|[^a-z0-9])acme([^a-z0-9]|$)")) (not (name "^draft"))) (move "Work/Acme/{mtime:%Y}") (stop)) .Ed .Pp Top-level forms may appear in any order, except that rules run in the order written. .Bl -tag -width Ds .It Ic (path Ar dir ) Required. The directory this file sorts. .Ar ~ expands. .It Ic (ignore Ar pattern No ...) May repeat; patterns accumulate in order. Gitignore syntax: .Ql * , .Ql ** , .Ql \&? , .Ql [...] ; a leading .Ql / anchors to the root; a trailing .Ql / matches directories only; .Ql \&! re-includes; a pattern with no .Ql / matches at any depth; the last matching pattern wins. An ignored directory is not descended into. .It Ic (exclude Ar condition No ...) May repeat. Sets matching files aside in this directory; see .Sx EXCLUSIONS . .It Ic (rule Ar name item No ...) See .Sx RULES . .El .Sh SETTINGS Each setting may appear in .Ic (defaults ...) , at the top of a directory file, or, where marked .Pq rule , inside a .Ic rule form. The most specific wins: rule, then directory, then defaults, then the built-in default below. Durations are an integer plus .Ql s m h d w ; sizes an integer plus an optional .Ql K M G T .Pq powers of 1024 . .Bl -tag -width Ds .It Ic case .Pq rule . .Sy ignore or .Sy strict : case sensitivity for .Ic name , path and .Ic content . Built-in: .Sy ignore . .It Ic fold .Pq rule . .Sy yes or .Sy no : strip diacritics before comparing .Po a with an ogonek becomes plain a, an l with a stroke becomes plain l, e acute becomes plain e, u with diaeresis becomes plain u, and so on for every Latin letter .Pc . Built-in: .Sy yes . .It Ic recursive .Sy yes or .Sy no . Built-in: .Sy no . .It Ic max-depth An integer; .Sy 1 means the root only. Built-in: unlimited. .It Ic min-age A duration. Files modified more recently are skipped as too new. .Xr krino 1 Ns 's .Fl -min-age overrides it for one run. Built-in: .Sy 2m . .It Ic max-read A size. No content is extracted from a file above this size. Built-in: .Sy 50M . .It Ic max-size A size. Files above it are skipped as too big: no rule sees them. Built-in: unlimited. .It Ic busy One or more suffixes. A file is skipped when its own name with one of these suffixes appended also exists beside it, e.g. .Pa report.pdf.part beside .Pa report.pdf . Built-in: .Sy .part .aria2 .crdownload . .It Ic on-conflict .Pq rule . .Sy suffix , skip or .Sy overwrite : see .Sx Conflicts . Built-in: .Sy suffix . .El .Sh EXCLUSIONS .Bd -literal -offset indent (exclude (type iso img)) ; by extension (exclude (name "^keep-")) ; by name (exclude (type pdf) (content "confidential")) ; by content .Ed .Pp An .Ic exclude form sets files aside before any rule sees them. Its conditions are those of .Sx CONDITIONS , and all of them must hold, as in .Ic when ; a file matching any .Ic exclude form is excluded. Forms in .Pa krino.conf apply to every directory and are tested first, then the directory's own. They use the directory's .Ic case and .Ic fold . .Pp An excluded file is counted as excluded in the plan and listed, with the form that matched, under .Fl v ; .Ic krino explain traces every form, and .Ic krino check lists them. No rule has run yet, so .Ic (matched) is never true inside an .Ic exclude . Unlike .Ic ignore , which never opens a file, an .Ic exclude can test size and content. .Sh RULES .Bd -literal -offset indent (rule NAME ITEM...) .Ed .Pp Items, in any order except that actions run in the order written: .Bl -tag -width Ds .It Ic (when Ar cond No ...) The condition; see .Sx CONDITIONS . Several conditions means all must hold. A rule with no .Ic when matches every file. An empty .Ic (when) is an error. .It Ic case , Ic fold , Ic on-conflict Rule-level settings; see .Sx SETTINGS . .It Ic (copy Ar dest ) Copy the file into directory .Ar dest . .It Ic (move Ar dest ) Move the file into directory .Ar dest . .It Ic (rename Ar name ) Rename in place. .Ar name must not contain .Ql / . .It Ic (delete) Move to the trash. .It Ic (delete permanent) Unlink. Cannot be undone. .It Ic (stop) Once this rule matches, evaluate no further rules for this file. .El .Pp A rule with only .Ic (stop) is an exclusion: files it matches receive no actions from later rules. .Pp A rule whose condition contains .Ic (duplicate) anywhere, including inside .Ic or or .Ic not , cannot contain .Ic (delete) or .Ic (delete permanent) ; .Ic krino check and every run refuse it .Pq Sx DUPLICATES . .Pp .Ar dest is a directory: a relative path is relative to the root, .Ql ~ expands, an absolute path is allowed, and it is created if missing. .Ar dest and .Ar name take placeholders; see .Sx Placeholders . .Sh CONDITIONS .Ss Operators .Ic (and Ar cond No ...) , .Ic (or Ar cond No ...) , .Ic (not Ar cond ) . .Ic and and .Ic or take one or more arguments; .Ic not exactly one. .Ss Tests .Bl -tag -width Ds .It Ic (type Ar t No ...) True when the name ends in .Ql \&. Ns Ar t for any .Ar t , case-insensitively. .Ar t may be a type group .Po e.g.\& .Ic document ; see .Sx TYPE GROUPS .Pc or a multi-part suffix like .Ic tar.gz . .It Ic (name Ar re No ...) True when the file name matches any of the regexes. .It Ic (path Ar re No ...) True when the path relative to the root matches any of the regexes. .It Ic (content Ar keyword No ...) True when the extracted text contains any of the keywords; see .Sx CONTENT EXTRACTION . .It Ic (size Ar op size ) .Ar op is one of .Ql > >= < <= = . .It Ic (age Ar op duration ) Age by modification time. .It Ic (duplicate) True when another scanned file has identical content and this one is not the chosen original; see .Sx DUPLICATES . .It Ic (duplicate Ar dir No ...) The same, also comparing against every regular file under .Ar dir . .It Ic (matched) True when an earlier rule already matched this file. .El .Pp Regexes use Go's RE2 syntax: POSIX extended regular expressions plus .Ql \ed \ew \es \eb , non-greedy quantifiers, and the inline flags .Ql (?i) and .Ql (?-i) ; there are no backreferences and no lookaround. .Ss Case, folding and word boundaries .Ic case and .Ic fold apply to .Ic name , path and .Ic content , in both the file's data and the pattern; .Ic type is always case-insensitive. A regex can override .Ic case locally with .Ql (?i) or .Ql (?-i) . .Pp .Sy RE2 treats .Ql _ as a word character. .Ql \eb is the boundary between a word character and a non-word character, so .Ql \ebacme\eb does .Em not match .Ar ACME_REPORT_2026.pdf : the .Ql E before the underscore and the underscore itself are both word characters, so there is no boundary there for .Ql \eb to match. Use a character class instead of .Ql \eb when the name may be glued to the rest with an underscore or a digit: .Bd -literal -offset indent (name "(^|[^a-z0-9])acme([^a-z0-9]|$)") .Ed matches .Ar ACME_REPORT_2026.pdf .Pq case-insensitively, by default because it treats anything that is not a lowercase letter or digit, including .Ql _ , as a separator. .Sh TYPE GROUPS A group name in .Ic (type ...) stands for every extension listed for it: .Bl -tag -width "presentation" .It Ic image jpg jpeg png gif webp bmp tif tiff heic heif avif svg ico raw cr2 nef arw dng .It Ic video mp4 mkv webm mov avi m4v mpg mpeg wmv flv 3gp .It Ic audio mp3 flac ogg opus m4a aac wav wma aiff .It Ic archive zip tar gz tgz bz2 tbz2 xz txz zst 7z rar lz lzma cpio .It Ic document pdf doc docx odt rtf txt md tex .It Ic spreadsheet xls xlsx ods csv tsv .It Ic presentation ppt pptx odp .It Ic ebook epub mobi azw azw3 fb2 djvu .It Ic code go c h cpp hpp py sh js ts rs java rb pl lua html css json yaml yml toml xml sql .It Ic text txt md log csv tsv json yaml yml toml xml ini conf .It Ic package deb rpm apk appimage exe msi flatpak snap .It Ic font ttf otf woff woff2 .El .Pp Groups overlap; a file can belong to several. .Sh CONTENT EXTRACTION .Bl -tag -width "presentation" .It Sy text Known text extensions, or detected from the first 8 KiB: valid UTF-8, or UTF-16 with a BOM, and no NUL bytes. .It Sy pdf .Ic pdftotext .Pq 30 second timeout . .It Sy docx, xlsx, pptx, odt, ods, odp, epub Zip plus streaming XML, Go standard library only. .It Sy html, xml Tags removed, entities decoded. .It Sy doc .Ic antiword , else .Ic catdoc . .It Sy xls / ppt .Ic xls2csv or .Ic catppt .Pq both from catdoc . .It Sy anything else No content. .El .Pp External tools are optional and looked up once per run; .Ic krino check lists which were found. A file above .Ic max-read is not extracted. Tools run without a shell and are given absolute paths only, so a file name starting with .Ql - is never read as an option, and each is killed at its timeout. .Pp Before matching, text and keywords are normalised the same way: case .Pq if Sy ignore , folding .Pq if Sy yes , and runs of whitespace collapsed to one space, so a keyword split across lines in a PDF still matches. Matching is substring: .Ql \&"acme\&" matches .Ql \&"acmeco\&" . Words hyphenated across lines in a PDF are not rejoined. .Ss Keyword cache For each file it extracts, .Nm krino records in .Pa $XDG_CACHE_HOME/krino/ Ns Ar name Ns Pa .cache which of the directory's content keywords the text contains, and answers later content tests from it while the file is unchanged. No text and no file names are stored; a file is known by device, inode, size and modification time, which a move or rename within one filesystem keeps. The keywords are stored as written in the configuration. .Pp A file is extracted again when it changes, or when a test asks about a keyword its entry was never checked against. Failures are never cached, and a file above .Ic max-read is refused before the cache is read. The whole cache is discarded when an extraction tool is installed, removed or replaced, or when krino's extraction or normalisation changes. Each run keeps entries only for files still in the directory. A file edited in place with its size and modification time preserved keeps its old answers; deleting the cache resets everything. .Sh ACTIONS .Ss Chains A file's chain is the actions of every matching rule, in rule order, and within a rule in the order written. .Ic copy leaves the file where it is; .Ic rename changes its name and .Ic move its directory, and later steps use the new path; .Ic delete ends the chain, and steps after it are shown in the plan as .Dq skipped: deleted by rule Ar x . A .Ic delete of a duplicate is itself skipped .Pq Sx DUPLICATES and does not end the chain. .Pp The plan warns when a chain moves a file more than once; that is usually a missing .Ic (stop) . .Ss How each action runs .Bl -tag -width Ds .It Ic move .Xr rename 2 when source and destination share a filesystem; otherwise a copy to a temporary file in the destination, fsync, rename into place, then the source is removed. If any step fails the source is untouched and the temporary file is removed. .It Ic copy To a temporary file in the destination, then renamed into place. Mode and modification time are preserved. .It Ic rename .Xr rename 2 within the same directory. .It Ic delete Into the freedesktop.org trash at .Pa $XDG_DATA_HOME/Trash . A file on a different filesystem from the trash is not trashed: the step fails with a message suggesting .Ic (delete permanent) or a move. .It Ic (delete permanent) .Xr unlink 2 . .El .Pp Before each step, the executor checks that the source still exists with the size and modification time recorded in the plan; if not, the step fails as .Dq changed since plan and the rest of that file's chain is skipped. .Ss Placeholders .Bl -tag -width "{mtime:FMT}" .It Ic {name} The current file name. .It Ic {stem} The name without its last extension. .It Ic {ext} The last extension with its dot, e.g. .Ql .pdf ; empty if none. .It Ic {1} No ... Ic {9} Capture groups of the first .Ic name test, not inside a .Ic not , that matched while evaluating this rule. .Ic krino check refuses a rule that uses .Ic { Ns Ar n Ns Ic } unless every such .Ic name test in it has at least .Ar n groups, and refuses one that uses .Ic { Ns Ar n Ns Ic } with no .Ic name test at all. .It Ic {mtime:FMT} The file's modification time. .It Ic {now:FMT} The start of the run. .It Ic {{ No and Ic }} A literal .Ql { or .Ql } . .El .Pp .Ar FMT is a strftime subset: .Ql %Y %m %d %H %M %S %j %% . .Ss Conflicts When the computed target already exists: .Bl -tag -width Ds .It Ic suffix .Pq default . Use .Pa stem_1.ext , stem_2.ext , and so on. .It Ic skip Skip this step; the chain continues from the file's current path. .It Ic overwrite Move the existing target to the trash first .Pq logged, so undo restores it , then proceed. .El .Pp A .Ic copy whose target already has identical content is skipped as .Dq already there , whatever the policy, so a backup rule can run every time. A step whose target is the file itself .Pq a Ar dest No that resolves to the file's own directory, or a Ic rename No to its current name is skipped as .Dq already there as well; a directory whose destination's first path component is a placeholder relies on this to avoid re-filing its own output \(em see .Sx KNOWN LIMITATIONS . .Pp Conflicts between files in the same plan are resolved when planning, so the plan shows final names; an in-plan claim is never displaced. The claim set spans the whole run, so two configured directories cannot plan the same final name. The executor re-checks at execution time; if the name has to change, the log records the actual name used. .Sh DUPLICATES Candidates are the scanned files plus, for .Ic (duplicate Ar dir ) , every regular file under .Ar dir .Pq symlinks skipped . Files are grouped by size; only groups of two or more are hashed, first the first and last 64 KiB, then SHA-256 of the whole file. Empty files are never duplicates. .Pp Two names for the same file .Pq the same device and inode, as a hard link creates are never duplicates of .Em each other ; they may still both independently be duplicates of a separate file with identical content. .Pp The .Em original in each group is, in order of preference: a file under one of the given .Ar dir arguments, then the oldest by modification time, then the shortest name, then the name that sorts first. .Ic (duplicate) is true for every other scanned file in the group. .Pp .Sy Warning: .Ic (duplicate) cannot tell a deliberate second copy from an accidental one. Byte-identical content is byte-identical either way; the distinction between .Dq I meant to keep two and .Dq this should not exist twice exists only in the user's intent, not in the files. A .Ic move rule over a directory that holds intentional copies .Pq a mirror, a staging queue, anything another tool manages will move things the user meant to keep there; keep such a rule on a directory scoped narrowly enough that every byte-identical pair in it really is an accident. .Pp .Sy Duplicates are found, never deleted. Deciding which copy to remove is left to the user, or to a tool built for it such as .Xr jdupes 1 . A rule combining .Ic (duplicate) with a delete action is refused .Pq Sx RULES . And in a directory whose rules use .Ic (duplicate) , a file that is a duplicate under any of the duplicate scopes those rules use \(em each distinct set of .Ar dir arguments, and the plain .Ic (duplicate) , looked up for the file whether or not evaluation reached that test \(em gets no delete step from any rule. The plan shows the step as .Dq skipped: a duplicate is never deleted , or, when that lookup fails, .Dq skipped: duplicate check failed, so not deleted: Ar reason ; the rest of the chain continues from the file's current path. This covers what the refusal cannot see: a later rule deleting through .Ic (matched) or through a condition of its own. .Pp Together they mean no rule can delete every copy of content a duplicate test in that directory can see. A file displaced by .Ic (on-conflict overwrite) is not covered; it goes to the trash, and .Ic krino undo restores it. .Pp The way to deal with duplicates is to move them aside and decide later: .Bd -literal -offset indent (rule "dupes" (when (duplicate "~/docs/Archive")) (move "~/.dupes/") (stop)) .Ed .Pp Every move is logged, so .Ic krino undo puts them back. 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 A .Ar dest whose .Em first path component is itself a placeholder .Pq e.g. Ic {ext} No or Ic {mtime:%Y} has no static prefix, so nothing under it can be excluded from the walk before scanning starts, and a recursive directory walks into .Ic krino Ns 's own output. A file already sitting at its own computed destination is a no-op, .Dq already there .Pq Sx Conflicts , rather than being re-filed on every run, but the walk still visits it, which a .Ic (duplicate) or .Ic content test in another rule can also see. Give such a rule its own narrow .Ic ignore pattern, or a .Ar dest whose first path component is a literal string, when that matters. .Sh SEE ALSO .Xr krino 1 .Pp .Pa docs/sexp-primer.md in the source repository .Pq installed at Pa $PREFIX/share/doc/krino/sexp-primer.md .