A string template processor: %{tags} in text are resolved when the text is used, each optionally piped through functions. Both a library and a command.
A template is ordinary text with tags in it. A tag is %{, a name, optional functions, }:
run-%{timestamp}-on-%{hostname}.log
%{inputWorkingFile|rmext}.jpg
%{timestamp|replace(:,-)|md5(6)}
%{"Subject12345"|sha256(8)}
Names resolve against the core tags (timestamp, date, hostname, platform, user, pid) and against dictionaries the calling program registers. Functions read left to right, each feeding the next; text after the closing } is kept. Nothing outside %{...} is a tag, so printf "%s", date +%Y%m%d and %20 in a URL pass through untouched. An unknown name is left as written unless --strict.
The full reference is docs/tags.adoc. The pf* family uses pftag wherever a user writes a template on the command line: pfdo_run's --exec string, pflog's telemetry URLs.
pip install pftag # no dependencies
pip install 'pftag[names]' # adds faker, for the dcmname pseudonym functionPython 3.11 or newer.
pftag --tag "run-%{timestamp}-on-%{hostname}.log"
pftag --tag "run-%{timestamp|replace(:,-)|md5(6)}.log"
pftag --tag "run-%{timestamp|strftime(%Y%m%d-%H%M)}.log"
pftag --lookupDictAdd '{"s": {"user": "chris"}}' --tag "user %{user}, hash %{user|md5(8)}"
pftag --tag "%{nope}" --strict # exit 1: unknown tag
pftag --tag "..." --json # the whole result: tags found, unknown, legacy, functions applied, errors
pftag --manimport shlex
from pftag import Pftag, TagOptions
tagger = Pftag(TagOptions(lookups={"run": {"subject": "sub-01", "file": "T1.nii.gz"}}))
tagger("%{subject}/%{file|rmext}.jpg").result # 'sub-01/T1.nii.jpg'
tagger.argv_process(shlex.split("convert %{file} %{file|rmext}.jpg")).argvprocess(template) is for text; argv_process(argv) is for commands: split the user's command first, then each argument is a template of its own, so a value with a space or a quote cannot change the shape of the command. lookup_add(name, values) registers a dictionary; a FunctionRegistry passed to Pftag takes new functions with add(name, fn), where fn(value, args) -> str.
| function | effect |
|---|---|
rmext, ext, basename, dirname |
path parts |
replace(a, b), re(pattern, repl) |
literal and regular-expression replacement |
upper, lower, strip[(chars)], pad(width[, char]), slice(start[, stop]) |
text shaping |
md5[(n)], sha256[(n)] |
hashing |
strftime(format) |
format a timestamp |
mask(pattern) |
* keeps a character, anything else replaces it |
dcmname[(seed)] |
a LAST^FIRST pseudonym, stable for a seed; needs pftag[names] |
- Tags are
%{...}only. The bare%nameform is gone; a bare%is always literal. A bare%nameof a known name is reported as a 1.x tag (and fails under--strict) so that old templates do not pass through silently. - Functions are
|fn(arg, arg)in pipe order; arguments bare or quoted;fnorfn(). A quoted head starts a chain from literal text, replacing%literalandecho. - Names may contain underscores;
%{dict.name}qualifies; two dictionaries defining one name is an error at registration. argv_processfor commands. Values are substituted once, never re-processed.- Functions renamed to common names:
replace,re,mask;ext,basename,dirname,strip,pad,slice,sha256,strftimeadded;chrplc,strrepl,strmsk,echogone.dcmnamemoves behind thenamesextra and seeds its own generator. - Core tags:
timestamp,date,hostname,platform,user,pid;os,release,machine,arch,literalgone. - No configurable markers, no dependencies, no
pudb/loguru/XDG_CONFIG_HOMEside effects;pyproject.tomlonly, CI on 3.11–3.14 and 3.14t, published from av*tag; the Docker image is no longer built.