Skip to main content

lz4/cli/
constants.rs

1//! CLI constants, globals, and display macros.
2//!
3//! This module centralises the values and shared mutable state needed across
4//! the CLI layer:
5//!
6//! - Identity strings (`COMPRESSOR_NAME`, `LZ4_EXTENSION`, …)
7//! - Binary size multipliers (`KB`, `MB`, `GB`)
8//! - The verbosity level used by [`displaylevel!`] and friends
9//! - The legacy-command flag that activates `lz4c`-style short options
10//! - The [`displayout!`], [`display!`], [`displaylevel!`], [`debugoutput!`],
11//!   and [`end_process!`] output macros used throughout the CLI
12
13use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
14
15// ── Identity strings ────────────────────────────────────────────────────────
16/// Primary compressor name, reported in `--version` output and as the default output extension.
17pub const COMPRESSOR_NAME: &str = "lz4";
18/// Library author credit shown in the welcome banner.
19pub const AUTHOR: &str = "Yann Collet";
20/// Default file extension appended to compressed output files.
21pub const LZ4_EXTENSION: &str = ".lz4";
22/// Canonical name for the decompression-only binary alias.
23pub const LZ4CAT: &str = "lz4cat";
24/// Canonical name for the decompression binary alias.
25pub const UNLZ4: &str = "unlz4";
26/// Name of the legacy `lz4c` binary whose short-option dialect this library supports.
27pub const LZ4_LEGACY: &str = "lz4c";
28
29/// Format string for the startup welcome banner.
30///
31/// Positional arguments (in order): compressor name, version, pointer-width in bits,
32/// threading mode string, author name.
33pub const WELCOME_MESSAGE_FMT: &str = "*** {} v{} {}-bit {}, by {} ***\n";
34
35// ── Binary size multipliers ─────────────────────────────────────────────────
36/// 1 KiB (1 024 bytes).
37pub const KB: u64 = 1 << 10;
38/// 1 MiB (1 048 576 bytes).
39pub const MB: u64 = 1 << 20;
40/// 1 GiB (1 073 741 824 bytes).
41pub const GB: u64 = 1 << 30;
42
43// ── Threading-mode label ────────────────────────────────────────────────────
44/// Human-readable threading mode inserted into the welcome banner.
45///
46/// Resolves to `"multithread"` when the `multithread` Cargo feature is enabled,
47/// or `"single-thread"` otherwise.
48#[cfg(feature = "multithread")]
49pub const IO_MT: &str = "multithread";
50#[cfg(not(feature = "multithread"))]
51pub const IO_MT: &str = "single-thread";
52
53// ── Verbosity level ──────────────────────────────────────────────────────────
54//
55// Controls how much output the CLI produces.  Semantics:
56//   0 — completely silent
57//   1 — errors only
58//   2 — normal informational output (default; can be suppressed with -q)
59//   3 — non-suppressible informational messages
60//   4 — verbose / diagnostic
61//
62// Stored as a process-wide atomic so it is accessible from any module without
63// threading through a context struct.
64pub static DISPLAY_LEVEL: AtomicU32 = AtomicU32::new(2);
65
66/// Returns the current verbosity level.
67#[inline]
68pub fn display_level() -> u32 {
69    DISPLAY_LEVEL.load(Ordering::Relaxed)
70}
71
72/// Sets the verbosity level.  Values outside 0–4 are accepted but have no
73/// additional effect beyond level 4.
74#[inline]
75pub fn set_display_level(level: u32) {
76    DISPLAY_LEVEL.store(level, Ordering::Relaxed);
77}
78
79// ── Legacy lz4c command mode ─────────────────────────────────────────────────
80//
81// When the binary is invoked as "lz4c", this flag is set to enable the
82// alternate short-option dialect: `-c0`, `-c1`, `-hc`, `-y`, etc.
83//
84// Stored as an atomic bool so it is visible across modules.  In unit tests,
85// prefer passing the flag explicitly rather than relying on this global.
86pub static LZ4C_LEGACY_COMMANDS: AtomicBool = AtomicBool::new(false);
87
88/// Returns `true` when the binary is running in legacy `lz4c` command mode.
89#[inline]
90pub fn lz4c_legacy_commands() -> bool {
91    LZ4C_LEGACY_COMMANDS.load(Ordering::Relaxed)
92}
93
94/// Enables (`true`) or disables (`false`) legacy `lz4c` command mode.
95#[inline]
96pub fn set_lz4c_legacy_commands(enabled: bool) {
97    LZ4C_LEGACY_COMMANDS.store(enabled, Ordering::Relaxed);
98}
99
100// ── Output macros ────────────────────────────────────────────────────────────
101//
102// Three tiers of CLI output:
103//   displayout!  — informational output that belongs on stdout (e.g. decompressed data)
104//   display!     — diagnostic output that always goes to stderr
105//   displaylevel! — conditional stderr output gated on the current verbosity level
106
107/// Write a formatted message to **stdout**.
108///
109/// Use this for output that is part of the compressed or decompressed data
110/// stream (e.g. when writing to a pipe), so it does not pollute stderr.
111#[macro_export]
112macro_rules! displayout {
113    ($($arg:tt)*) => { print!($($arg)*) };
114}
115
116/// Write a formatted message to **stderr** unconditionally.
117///
118/// Prefer [`displaylevel!`] when the message should be suppressible.
119#[macro_export]
120macro_rules! display {
121    ($($arg:tt)*) => { eprint!($($arg)*) };
122}
123
124/// Write a formatted message to **stderr** if the current verbosity level is
125/// at least `level`.
126///
127/// | `level` | meaning |
128/// |---------|----------------------------|
129/// | 1       | errors only |
130/// | 2       | normal (default) |
131/// | 3       | non-suppressible info |
132/// | 4       | verbose / diagnostic |
133#[macro_export]
134macro_rules! displaylevel {
135    ($level:expr, $($arg:tt)*) => {
136        if $crate::cli::constants::display_level() >= $level {
137            eprint!($($arg)*);
138        }
139    };
140}
141
142// ── Debug and fatal-error macros ─────────────────────────────────────────────
143//
144// `debugoutput!` — emits to stderr in debug builds only; a no-op in release.
145// `end_process!` — prints a diagnostic then terminates the process, used for
146//                  unrecoverable CLI errors (bad arguments, I/O failures, etc.).
147
148/// Write a formatted message to **stderr** in debug builds only.
149///
150/// Compiled away entirely in release builds (`--release` / no `debug_assertions`).
151#[macro_export]
152macro_rules! debugoutput {
153    ($($arg:tt)*) => {
154        #[cfg(debug_assertions)]
155        eprint!($($arg)*);
156    };
157}
158
159/// Print an error diagnostic and exit the process with the given code.
160///
161/// In debug builds, also emits the source file and line number before the
162/// message.  The error message is only printed when the verbosity level is ≥ 1
163/// (i.e. not completely silent).
164///
165/// # Example
166/// ```ignore
167/// end_process!(1, "cannot open '{}'", path);
168/// ```
169#[macro_export]
170macro_rules! end_process {
171    ($error:expr, $($arg:tt)*) => {{
172        // In debug builds, include the source location for easier triage.
173        #[cfg(debug_assertions)]
174        eprint!("Error in {}, line {} : \n", file!(), line!());
175        // Respect a verbosity of 0 (fully silent mode).
176        if $crate::cli::constants::display_level() >= 1 {
177            eprint!("Error {} : ", $error);
178            eprint!($($arg)*);
179            eprint!("\n");
180        }
181        std::process::exit($error as i32);
182    }};
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188
189    #[test]
190    fn extension_constant() {
191        assert_eq!(LZ4_EXTENSION, ".lz4");
192    }
193
194    #[test]
195    fn compressor_name_constant() {
196        assert_eq!(COMPRESSOR_NAME, "lz4");
197    }
198
199    #[test]
200    fn size_constants() {
201        assert_eq!(KB, 1024);
202        assert_eq!(MB, 1024 * 1024);
203        assert_eq!(GB, 1024 * 1024 * 1024);
204    }
205
206    #[test]
207    fn display_level_default() {
208        // Default is 2 (normal, downgradable).
209        // Note: other tests may mutate this; reset after checking.
210        let prev = display_level();
211        // Confirm the accessor works
212        assert!(display_level() <= 4);
213        // Confirm setter round-trips
214        set_display_level(3);
215        assert_eq!(display_level(), 3);
216        set_display_level(prev);
217    }
218
219    #[test]
220    fn legacy_commands_default_false() {
221        // Reset to known state first — parallel tests in other modules may have mutated the global.
222        set_lz4c_legacy_commands(false);
223        assert!(!lz4c_legacy_commands());
224        set_lz4c_legacy_commands(true);
225        assert!(lz4c_legacy_commands());
226        set_lz4c_legacy_commands(false);
227    }
228}