rusty-ts 0.1.0

Prefix each line of stdin with a timestamp — a Rust port of moreutils `ts` with strict-compat mode, IANA timezone control, and a reusable byte-typed library API.
Documentation

rusty-ts

A Rust port of the moreutils ts utility: prefix each line of stdin with a timestamp. Static binaries on Linux, macOS, and Windows; works with or without a Rust toolchain via cargo install or cargo binstall. Default mode adds a few niceties moreutils doesn't have (-u/--utc, --tz=<IANA>, env-var defaults, shell completions); Strict mode reverts every observable surface to byte-identical moreutils behavior for drop-in migration.

Part of the Rusty portfolio — a collection of small Rust ports of utilities missing from the Rust ecosystem.

Install

With a Rust toolchain

cargo install rusty-ts

To also install the ts binary alias (auto-enables Strict mode on invocation):

cargo install rusty-ts --features ts-alias

Without a Rust toolchain (prebuilt binaries via cargo-binstall)

cargo binstall rusty-ts

Direct download

Per-target archives are attached to each GitHub Release. Linux x86_64/aarch64, macOS x86_64/aarch64, Windows x86_64. Each archive contains the binary plus pre-generated shell-completion scripts for bash, zsh, fish, and PowerShell.

Usage

# Default format (matches moreutils ts: `%b %d %H:%M:%S`)
some-command | rusty-ts

# Custom strftime format
some-command | rusty-ts '%Y-%m-%d %H:%M:%S'

# Elapsed time since previous line / since program start (monotonic clock)
some-command | rusty-ts -i
some-command | rusty-ts -s -m

# UTC or named IANA timezone
some-command | rusty-ts -u
some-command | rusty-ts --tz=America/New_York

# Convert pre-timestamped lines to relative form (Default-mode subset: ISO-8601, RFC-3339, Unix epoch)
cat logfile | rusty-ts -r

# Strict moreutils-compat mode (rejects -u/--tz, expands -r to full moreutils set, mirrors stderr layout)
some-command | rusty-ts --strict
RUSTY_TS_STRICT=1 some-command | rusty-ts
some-command | ts          # via the ts-alias feature or a symlink — argv[0] auto-detect

# Implicit default format via env var (Default mode only)
RUSTY_TS_FORMAT='[%H:%M:%S]' some-command | rusty-ts

# Shell completions
rusty-ts completions bash    # > ~/.bash_completion.d/rusty-ts
rusty-ts completions zsh     # > ~/.zfunc/_rusty-ts
rusty-ts completions fish    # > ~/.config/fish/completions/rusty-ts.fish
rusty-ts completions powershell

Compatibility statement (vs moreutils ts)

Byte-level fidelity is verified by snapshot tests against captured moreutils-ts output under a pinned environment: TZ=UTC and LC_ALL=C.UTF-8. The snapshot reference is moreutils at a pinned upstream commit recorded in fixtures/README.md.

Documented intentional divergences from moreutils ts (also enumerated in docs/COMPATIBILITY.md — generated from the CLI definition and drift-tested in CI):

  1. -r recognized-timestamp set is a subset in Default mode: ISO-8601, RFC-3339, and Unix epoch (integer + fractional) only. --strict expands recognition to the full moreutils set.
  2. -u / --utc flag: not present in moreutils. Default-mode addition; rejected in Strict mode.
  3. --tz=<IANA> flag: not present in moreutils. Default-mode addition; rejected in Strict mode.
  4. RUSTY_TS_FORMAT env var: not defined by moreutils. Honored in Default mode; ignored in Strict mode.
  5. completions subcommand: not present in moreutils. Default-mode addition; rejected in Strict mode.

In Strict mode, exit codes, stderr diagnostic text, and --help / --version layouts match moreutils. See docs/COMPATIBILITY.md for the full per-flag matrix and exit-code table.

Library API

The crate exposes a public Rust API for programmatic use. The canonical surface is byte-typed (preserves non-UTF-8 payload bytes per FR-011); a String-typed convenience adapter is available for the common UTF-8 case.

use rusty_ts::{TimestamperBuilder, Format, CompatibilityMode, TimezoneSource};
use std::io::{BufReader, Cursor};

let mut ts = TimestamperBuilder::new()
    .format(Format::Strftime("%Y-%m-%d %H:%M:%S".into()))
    .compat(CompatibilityMode::Default)
    .timezone(TimezoneSource::Utc)
    .build()?;

let input = BufReader::new(Cursor::new("hello\nworld\n"));
for line in ts.prefix_lines(input) {
    let bytes = line?;
    print!("{}", String::from_utf8_lossy(&bytes));
}
# Ok::<(), Box<dyn std::error::Error>>(())

To use the library without pulling in the CLI dependencies:

[dependencies]
rusty-ts = { version = "0.1", default-features = false }

Relationship to moreutils

rusty-ts is a clean-room Rust reimplementation of the moreutils ts utility. It contains no source code from moreutils — only a from-scratch Rust implementation that observes the documented behavior of moreutils ts and reproduces it.

The moreutils ts Perl script is © 2006 Joey Hess and licensed under the GNU GPL. That license governs the Perl source code. Behavioral interfaces (flag set, output format) are not copyrightable, so a clean-room reimplementation under a different license is well-established practice — the same posture as uutils/coreutils (MIT-licensed reimplementation of GPL-licensed GNU coreutils).

rusty-ts does not distribute or derive from the moreutils source code. Snapshot tests in this repository compare rusty-ts runtime output against captured moreutils ts runtime output (captured by running moreutils against fixtures and recording bytes) — that is not source-code derivation either. The captured output bytes are facts, not creative expression.

If you want the original moreutils ts, install it via your platform's package manager (apt install moreutils, brew install moreutils, etc.) — that is unaffected by this port's existence.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.