Skip to main content

curtail

Curtail (cut the tail) - reduce arbitrary text to a special string by cutting short.

Feed it any string — unbalanced emphasis markers, mid-word cuts, mixed code spans, raw user content — and it always returns a string that is both short enough and well-formed inline markdown.

from curtail import clip

clip('**bold text** and more _italic_', 20)
# → '**bold** (cont.)'

clip('`code with *stars*', 12, '…')
# → '`code with`…'

No runtime dependencies. Pure Python ≥ 3.11.

Install

pip install curtail

Manual

The man page provides the CLI reference (man curtail after placing the file on your MANPATH):

mkdir -p ~/.local/share/man/man1
cp docs/curtail.1 ~/.local/share/man/man1/

Quickstart

Run curtail with no arguments for a usage summary. For a dedicated feature walkthrough you can follow in minutes visit quickstart. A step-by-step guided build of a document pipeline is provided in the tutorial.

Library

clip(text, max_length, indicator=' (cont.)', *, word_boundary=True)

Returns a string of at most max_length characters that is valid inline markdown.

from curtail import clip

# Truncation with default indicator
clip('**bold text** and more _italic_', 20)
# → '**bold** (cont.)'

# Custom indicator
clip('**bold text** and more _italic_', 16, '…')
# → '**bold text**…'

# Hard cut (no word-boundary snapping)
clip('hello world', 8, '...', word_boundary=False)
# → 'hello...'

# Unbalanced input is closed automatically
clip('**unfinished bold', 30)
# → '**unfinished bold**'

Guarantees — both hold unconditionally for every input:

  • len(result) ≤ max_length
  • result is well-formed inline markdown (all opened spans are closed)

patch(text, *, replace=None, stop_at=None)

Pre-process text before passing it to clip. Apply character replacements, truncate at a sentinel, or both.

from curtail import patch, clip

# Strip everything from the first newline
patch('first line\nsecond line', stop_at='\n')
# → 'first line'

# Normalise whitespace variants
patch('line\r\nend', replace={'\r': '', '\n': ' '})
# → 'line end'

# Escape pipe characters for a Markdown table cell, then truncate
cell = patch(raw, replace={'|': r'\|'}, stop_at='\n')
preview = clip(cell, 60, '…')

stop_at truncates before replace applies, so stop characters are never transformed — they simply end the string. Replacements inside backtick code spans are skipped so that literal content is never altered.

clip_cell(text, max_length, indicator='…', *, word_boundary=True)

Convenience wrapper for Markdown table cells. Combines patch and clip in one call: trims to the first line, escapes |, then truncates.

from curtail import clip_cell

clip_cell('**bold** value with a|pipe\nand more', 40)
# → '**bold** value with a\|pipe…'

Guarantees — in addition to the two clip guarantees:

  • Result contains no unescaped | characters.
  • Result contains no \n characters.

code(text, connector='-', marker=chr(30), gremlins=GREMLINS, policy='lower')

Derive a kebab-style identifier from arbitrary text.

from curtail import code, DASH, RS, GREMLINS

code('Hello, World!')          # → 'hello-world'
code('foo-bar baz')            # → 'foo-bar-baz'  (connector preserved)
code('__init__')               # → 'init'
code('hello world', policy='upper')   # → 'HELLO-WORLD'
code('hello world', connector='_')    # → 'hello_world'

Connector characters already present in the input are preserved through a sandwich transform: they are encoded as marker before gremlin replacement and restored afterwards, so 'foo-bar' and 'foo bar' produce different identifiers.

Three module-level constants expose the defaults:

from curtail import DASH, RS, GREMLINS

DASH     # '-'
RS       # chr(30)  — ASCII Record Separator (sandwich-transform marker)
GREMLINS # ' .,;?!_()[]{}<>\\/$:"\'`´'

CLI

Calling curtail with no arguments prints the help screen and exits 0.

Unique prefix matching is supported for all subcommands (cl → clip, ce → cell, etc.).

curtail [-V] clip   -n N [-i STR] [--hard-cut] [--stop-at CHARS] [--replace FROM TO] [text]
curtail [-V] cell   -n N [-i STR] [--hard-cut] [text]
curtail [-V] code   [--connector STR] [--gremlins STR] [--policy METHOD] [--marker STR] [text]
curtail [-V] patch  [--stop-at CHARS] [--replace FROM TO] [text]
curtail [-V] version

When text is omitted the input is read from stdin.

# Clip: basic truncation
echo "**bold text** and more _italic_" | curtail clip -n 20
# → **bold** (cont.)

# Cell: table-cell pipeline (pipes escaped, newlines dropped)
curtail cell -n 60 -i '…' "$(cat cell.txt)"

# Clip with pre-processing: stop at newline, escape pipe
curtail clip -n 60 -i '…' --stop-at $'\n' --replace '|' '\|' "$(cat cell.txt)"

# Code: identifier from a heading
curtail code 'Hello, World!'
# → hello-world

# Code with custom connector and upper-case policy
curtail code --connector '_' --policy upper 'hello world'
# → HELLO_WORLD

# Patch: pre-process without truncating
curtail patch --stop-at $'\n' 'first line\nsecond line'
# → first line
Subcommand Flag Default Meaning
clip -n N (required) Maximum output length
clip -i STR ' (cont.)' Continuation indicator
clip --hard-cut off Character-level cut instead of word boundary
clip --stop-at CHARS — Truncate at first occurrence of any of these characters
clip --replace FROM TO — Replace character FROM with string TO (repeatable)
cell -n N (required) Maximum output length
cell -i STR '…' Continuation indicator
cell --hard-cut off Character-level cut instead of word boundary
code --connector STR '-' Separator between identifier parts
code --gremlins STR GREMLINS Characters replaced by the connector
code --policy METHOD lower str method applied to the final identifier
code --marker STR chr(30) Sandwich-transform placeholder (rarely needed)
patch --stop-at CHARS — Truncate at first occurrence of any of these characters
patch --replace FROM TO — Replace character FROM with string TO (repeatable)
all -V, --version — Print version and exit

Tracked inline constructs

Construct Opener Closer
Code span `, ``, … matching run of backticks
Emphasis * or _ matching * or _
Strong ** or __ matching ** or __

Underscore emphasis follows CommonMark flanking rules: _ surrounded by alphanumeric characters on both sides (intra-word) is treated as literal text.

Links, images, block-level elements, HTML tags, and entity references are intentionally left unchanged — they either render safely as literal text (unclosed [) or are the caller's responsibility.

Development

# Tests (238 cases including Hypothesis property tests)
make test

# Branch coverage (100 % on both implementation modules)
make coverage

# Format, lint, type-check, security scan
make quality

# Coverage-guided fuzzing (requires python-afl and AFL++)
make fuzz-scan fuzz_time=300
make corpus-update          # merge → minimise → promote findings

Design and requirements

Document Identifier File
Software Requirements Specification CRT-SRS-001 requirements/srs/
Software Design Description CRT-SDD-001 design/sdd/

Both documents follow the MIL-STD-498 DID structure and are rendered into the documentation site alongside the quickstart and tutorial.

Bug Tracker

Any feature requests or bug reports shall go to the todos of curtail.

Primary Source repository

The main source of curtail is on a mountain in Central Switzerland under configuration control (fossil).

Contributions

If you like to share small changes under the repositories license please kindly do so by sending a patchset. You can send such a patchset per email using git send-email.

Support

Please kindly submit issues at https://todo.sr.ht/~sthagen/curtail or write plain text email to ~sthagen/curtail@lists.sr.ht to support. Thanks.

Changes

See docs/changes.md for the release history.

Coverage

The test suite maintains 100% branch coverage on both implementation modules. The HTML report (if generated) is in site/coverage/.

SBOM

Runtime dependency information is published in docs/sbom/ in SPDX 2.3 (JSON) and CycloneDX 1.5 (JSON) formats. See docs/sbom/README.md for the component inventory and validation guide.

Metadata

Release files for curtail 2026.7.8

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for curtail 2026.7.8
File Size Uploaded
curtail-2026.7.8.tar.gz 25.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for curtail 2026.7.8
File Interpreter ABI Platform
curtail-2026.7.8-py3-none-any.whl Python 3 none any Details

Total release size: 38.5 kB

Release files / curtail-2026.7.8.tar.gz

Download URL curtail-2026.7.8.tar.gz
Size 25.5 kB
Tags Source
SHA-256 checksum
How to use checksums
6eb282145b1e23cf43520f217e9fde615a624e6b08e013dd7ac080a34b2db9d6
BLAKE2b-256 checksum
How to use checksums
593e9394c149d484a9b04734426c282eb9a5b03f2ecc29957b6d0519789c8921
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.5

Release files / curtail-2026.7.8-py3-none-any.whl

Download URL curtail-2026.7.8-py3-none-any.whl
Size 13.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d287aa280915104f653d0657adad621c279f5aac734df17840b8b481b335e07d
BLAKE2b-256 checksum
How to use checksums
cc1a575013ea1c2a4ccf2db1b3af6ff762e159dd5e065be1d933dba42e890669
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.5

Release history Release notifications | RSS feed

This release

2026.7.8 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page