summaryrefslogtreecommitdiff
path: root/man/krino.conf.5
diff options
context:
space:
mode:
Diffstat (limited to 'man/krino.conf.5')
-rw-r--r--man/krino.conf.5696
1 files changed, 696 insertions, 0 deletions
diff --git a/man/krino.conf.5 b/man/krino.conf.5
new file mode 100644
index 0000000..fbd1c06
--- /dev/null
+++ b/man/krino.conf.5
@@ -0,0 +1,696 @@
+.\" SPDX-License-Identifier: GPL-3.0-or-later
+.Dd September 13, 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))
+.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 .
+.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
+
+(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 (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 busy.
+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 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 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
+.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.
+.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 .
+.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.
+Pairing
+.Ic (duplicate)
+with
+.Ic (delete)
+over a directory that may hold intentional copies
+.Pq a mirror, a staging queue, anything another tool manages
+will delete things the user meant to keep.
+Keep such a rule on a directory scoped narrowly enough that every
+byte-identical pair in it really is an accident.
+.Pp
+.Sy Warning:
+each
+.Ic (duplicate)
+or
+.Ic (duplicate Ar dir )
+condition elects its own original from its own candidates; two conditions
+with different scopes can therefore elect
+.Em different
+originals for the same content.
+Within one directory's rules
+.Pq one rule combining conditions with Ic or , or two separate rules ,
+this can make every copy in a group selected by some condition, and with
+.Ic (delete permanent)
+every copy selected is gone for good.
+.Ic krino
+plans and applies one directory at a time, so a directory already applied
+in the run stays applied while a later directory is planned; a
+.Fl n
+run only ever plans, so it shows every directory's plan as if none of the
+others had been applied.
+Two files in different directories that a
+.Fl n
+plan each lists as a duplicate of the other are therefore not necessarily
+both deleted when the same run is applied with
+.Fl y :
+deleting the first can remove the very original the second file was a
+duplicate of, leaving the second no longer a duplicate by the time its own
+directory is planned.
+Use one duplicate scope for rules that delete within a directory; prefer
+.Ic (delete)
+to
+.Ic (delete permanent)
+with
+.Ic (duplicate) ;
+and within one directory's rules, two files each listed as a duplicate of
+the other in the
+.Fl n
+plan means both would be 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.
+.Pp
+.Ic (duplicate)
+conditions with different scopes do not share an original: two conditions
+electing from different candidate sets can each treat a different file as
+the original of the same content
+.Pq Sx DUPLICATES .
+.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 .