Skip to content

fix: keep piped output in order and drop italics without colors - #369

Open
mirfalltnixein wants to merge 1 commit into
tldr-pages:mainfrom
mirfalltnixein:fix/piped-output
Open

mirfalltnixein wants to merge 1 commit into
tldr-pages:mainfrom
mirfalltnixein:fix/piped-output

Conversation

@mirfalltnixein

@mirfalltnixein mirfalltnixein commented Oct 22, 2026 •

Copy link
Copy Markdown

I, the human behind this, have run into this problem. I've reviewed this code and am willing to put my name on it.

Fixes #367
Fixes #368

Problem

When stdout is not a terminal (e.g. tldr tar | cat):

Cause

  • output() writes the page lines with sys.stdout.buffer.write() (raw bytes, added in improve handling of different languages #112 so pages are always UTF-8) but the newlines with print(). print() goes through the text layer, which has its own buffer. On a terminal that buffer is flushed every line, so the order looks right; on a pipe it is block-buffered, so all the print() newlines arrive after the byte writes.
  • The italics are raw \x1B[3m / \x1B[23m escapes, added around colored(), so they are emitted even when termcolor has decided not to color the output.

The existing test_whole_page replaces sys.stdout.buffer with a stub that writes into the same StringIO as print(), which is why it never showed up in the tests.

Fix

  • output() writes everything as bytes, including the --markdown lines (otherwise they would be reordered against the final newline), and flushes pending text output first so messages printed before the page (e.g. the TLDR_PLATFORM warning) stay in front of it.
  • emphasise_example only adds the italics when colored() actually styled the text. (termcolor has an italic attribute only since 3.3.0, so I didn't rely on it.)
  • New test test_whole_page_piped renders the gem and jq pages in a subprocess through a real pipe, with and without FORCE_COLOR. Without colors, the expected output is the rendered file with all ANSI codes stripped. It skips the no-color case on termcolor < 2.1, which doesn't disable colors when not on a TTY.

One behavior change: on Windows, the newlines written by output() are now \n instead of print()'s \r\n. The page lines themselves already bypassed the text layer.

Testing

  • pytest: 35 passed on Python 3.10 and 3.14; with termcolor 1.1.0 and 2.0.0, 33 passed and the 2 no-color cases skipped; 2.1.0, 2.4.0 and 3.3.0 all pass. flake8 clean.
  • The 4 new test cases fail on current main, and also fail if the sys.stdout.flush() is removed.
  • tldr tar | cat -A now prints the page line by line with no escape codes; FORCE_COLOR=1 tldr echo | cat -A keeps the colors and italics.

I used an AI assistant (Claude Code) to write this change. I reviewed the code and the tests before submitting.

🤖 Generated with Claude Code

output() wrote page lines with sys.stdout.buffer.write() but the
newlines with print(). The text layer has its own buffer, which is only
flushed per line on a terminal, so when stdout is a pipe all the
newlines came out at the end, after the joined page lines. Write
everything in output() as bytes (including the --markdown lines, which
would otherwise reorder against the final newline) and flush pending
text output first.

The italics around backticked text in example descriptions were raw
ANSI escapes, so they were printed even when termcolor disables colors
(e.g. when piped). Only add them when colored() actually styled the
text.

Add a test that renders the test pages through a real pipe, with and
without FORCE_COLOR.

Fixes tldr-pages#367
Fixes tldr-pages#368

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Piping tldr does not strip all style Nonsense output at the end of tldr when piped

1 participant