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}