1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-27 11:07:44 +00:00

Merge pull request #8243 from sylvestre/l10n-stat

l10n: port stat for translation + use thiserror + add french
This commit is contained in:
Daniel Hofstetter 2025-06-29 14:40:48 +02:00 committed by GitHub
commit fd7661c62e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 333 additions and 70 deletions

1
Cargo.lock generated
View file

@ -3671,6 +3671,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"clap", "clap",
"thiserror 2.0.12",
"uucore", "uucore",
] ]

View file

@ -21,6 +21,7 @@ path = "src/stat.rs"
clap = { workspace = true } clap = { workspace = true }
uucore = { workspace = true, features = ["entries", "libc", "fs", "fsext"] } uucore = { workspace = true, features = ["entries", "libc", "fs", "fsext"] }
chrono = { workspace = true } chrono = { workspace = true }
thiserror = { workspace = true }
[features] [features]
selinux = ["uucore/selinux"] selinux = ["uucore/selinux"]

View file

@ -52,3 +52,59 @@ stat-after-help = Valid format sequences for files (without `--file-system`):
NOTE: your shell may have its own version of stat, which usually supersedes NOTE: your shell may have its own version of stat, which usually supersedes
the version described here. Please refer to your shell's documentation the version described here. Please refer to your shell's documentation
for details about the options it supports. for details about the options it supports.
## Error messages
stat-error-invalid-quoting-style = Invalid quoting style: {$style}
stat-error-missing-operand = missing operand
Try 'stat --help' for more information.
stat-error-invalid-directive = {$directive}: invalid directive
stat-error-cannot-read-filesystem = cannot read table of mounted file systems: {$error}
stat-error-stdin-filesystem-mode = using '-' to denote standard input does not work in file system mode
stat-error-cannot-read-filesystem-info = cannot read file system information for {$file}: {$error}
stat-error-cannot-stat = cannot stat {$file}: {$error}
## Warning messages
stat-warning-backslash-end-format = backslash at end of format
stat-warning-unrecognized-escape-x = unrecognized escape '\x'
stat-warning-incomplete-hex-escape = incomplete hex escape '\x'
stat-warning-unrecognized-escape = unrecognized escape '\{$escape}'
## Help messages
stat-help-dereference = follow links
stat-help-file-system = display file system status instead of file status
stat-help-terse = print the information in terse form
stat-help-format = use the specified FORMAT instead of the default;
output a newline after each use of FORMAT
stat-help-printf = like --format, but interpret backslash escapes,
and do not output a mandatory trailing newline;
if you want a newline, include \n in FORMAT
## Word translations
stat-word-file = File
stat-word-id = ID
stat-word-namelen = Namelen
stat-word-type = Type
stat-word-block = Block
stat-word-size = size
stat-word-fundamental = Fundamental
stat-word-block-size = block size
stat-word-blocks = Blocks
stat-word-total = Total
stat-word-free = Free
stat-word-available = Available
stat-word-inodes = Inodes
stat-word-device = Device
stat-word-inode = Inode
stat-word-links = Links
stat-word-io = IO
stat-word-access = Access
stat-word-uid = Uid
stat-word-gid = Gid
stat-word-modify = Modify
stat-word-change = Change
stat-word-birth = Birth
## SELinux context messages
stat-selinux-failed-get-context = failed to get security context
stat-selinux-unsupported-system = unsupported on this system
stat-selinux-unsupported-os = unsupported for this operating system

View file

@ -0,0 +1,109 @@
stat-about = afficher le statut du fichier ou du système de fichiers.
stat-usage = stat [OPTION]... FICHIER...
stat-after-help = Séquences de format valides pour les fichiers (sans `--file-system`) :
-`%a` : droits d'accès en octal (note : drapeaux printf '#' et '0')
-`%A` : droits d'accès en format lisible
-`%b` : nombre de blocs alloués (voir %B)
-`%B` : la taille en octets de chaque bloc rapporté par %b
-`%C` : chaîne de contexte de sécurité SELinux
-`%d` : numéro de périphérique en décimal
-`%D` : numéro de périphérique en hexadécimal
-`%f` : mode brut en hexadécimal
-`%F` : type de fichier
-`%g` : ID de groupe du propriétaire
-`%G` : nom de groupe du propriétaire
-`%h` : nombre de liens physiques
-`%i` : numéro d'inode
-`%m` : point de montage
-`%n` : nom de fichier
-`%N` : nom de fichier avec guillemets et déréférencement (suivi) si lien symbolique
-`%o` : suggestion de taille optimale de transfert E/S
-`%s` : taille totale, en octets
-`%t` : type de périphérique majeur en hex, pour les fichiers spéciaux caractère/bloc
-`%T` : type de périphérique mineur en hex, pour les fichiers spéciaux caractère/bloc
-`%u` : ID utilisateur du propriétaire
-`%U` : nom d'utilisateur du propriétaire
-`%w` : heure de création du fichier, lisible ; - si inconnue
-`%W` : heure de création du fichier, secondes depuis l'Époque ; 0 si inconnue
-`%x` : heure du dernier accès, lisible
-`%X` : heure du dernier accès, secondes depuis l'Époque
-`%y` : heure de la dernière modification de données, lisible
-`%Y` : heure de la dernière modification de données, secondes depuis l'Époque
-`%z` : heure du dernier changement de statut, lisible
-`%Z` : heure du dernier changement de statut, secondes depuis l'Époque
Séquences de format valides pour les systèmes de fichiers :
-`%a` : blocs libres disponibles pour les non-superutilisateurs
-`%b` : blocs de données totaux dans le système de fichiers
-`%c` : nœuds de fichiers totaux dans le système de fichiers
-`%d` : nœuds de fichiers libres dans le système de fichiers
-`%f` : blocs libres dans le système de fichiers
-`%i` : ID du système de fichiers en hexadécimal
-`%l` : longueur maximale des noms de fichiers
-`%n` : nom de fichier
-`%s` : taille de bloc (pour des transferts plus rapides)
-`%S` : taille de bloc fondamentale (pour les comptes de blocs)
-`%t` : type de système de fichiers en hexadécimal
-`%T` : type de système de fichiers en format lisible
NOTE : votre shell peut avoir sa propre version de stat, qui remplace généralement
la version décrite ici. Veuillez vous référer à la documentation de votre shell
pour les détails sur les options qu'il prend en charge.
# Messages d'aide
stat-help-dereference = suivre les liens
stat-help-file-system = afficher le statut du système de fichiers au lieu du statut du fichier
stat-help-terse = afficher les informations en forme concise
stat-help-format = utiliser le FORMAT spécifié au lieu du défaut ;
afficher une nouvelle ligne après chaque utilisation de FORMAT
stat-help-printf = comme --format, mais interpréter les séquences d'échappement avec barre oblique inverse,
et ne pas afficher une nouvelle ligne finale obligatoire ;
si vous voulez une nouvelle ligne, incluez \n dans FORMAT
## Traductions de mots
stat-word-file = Fichier
stat-word-id = ID
stat-word-namelen = Longnom
stat-word-type = Type
stat-word-block = Bloc
stat-word-size = taille
stat-word-fundamental = Fondamentale
stat-word-block-size = taille bloc
stat-word-blocks = Blocs
stat-word-total = Total
stat-word-free = Libres
stat-word-available = Disponibles
stat-word-inodes = Inodes
stat-word-device = Périphérique
stat-word-inode = Inode
stat-word-links = Liens
stat-word-io = E/S
stat-word-access = Accès
stat-word-uid = Uid
stat-word-gid = Gid
stat-word-modify = Modif
stat-word-change = Changt
stat-word-birth = Créé
## Messages d'erreur
stat-error-invalid-quoting-style = Style de guillemets invalide : {$style}
stat-error-missing-operand = opérande manquant
Essayez 'stat --help' pour plus d'informations.
stat-error-invalid-directive = {$directive} : directive invalide
stat-error-cannot-read-filesystem = impossible de lire la table des systèmes de fichiers montés : {$error}
stat-error-stdin-filesystem-mode = utiliser '-' pour désigner l'entrée standard ne fonctionne pas en mode système de fichiers
stat-error-cannot-read-filesystem-info = impossible de lire les informations du système de fichiers pour {$file} : {$error}
stat-error-cannot-stat = impossible d'obtenir le statut de {$file} : {$error}
## Messages d'avertissement
stat-warning-backslash-end-format = barre oblique inverse à la fin du format
stat-warning-unrecognized-escape-x = séquence d'échappement non reconnue '\x'
stat-warning-incomplete-hex-escape = séquence d'échappement hexadécimale incomplète '\x'
stat-warning-unrecognized-escape = séquence d'échappement non reconnue '\{$escape}'
## Messages de contexte SELinux
stat-selinux-failed-get-context = impossible d'obtenir le contexte de sécurité
stat-selinux-unsupported-system = non pris en charge sur ce système
stat-selinux-unsupported-os = non pris en charge pour ce système d'exploitation

View file

@ -4,7 +4,7 @@
// file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore datetime // spell-checker:ignore datetime
use uucore::error::{UResult, USimpleError}; use uucore::error::{UError, UResult, USimpleError};
use clap::builder::ValueParser; use clap::builder::ValueParser;
use uucore::display::Quotable; use uucore::display::Quotable;
@ -26,7 +26,33 @@ use std::os::unix::prelude::OsStrExt;
use std::path::Path; use std::path::Path;
use std::{env, fs}; use std::{env, fs};
use uucore::locale::get_message; use std::collections::HashMap;
use thiserror::Error;
use uucore::locale::{get_message, get_message_with_args};
#[derive(Debug, Error)]
enum StatError {
#[error("{}", get_message_with_args("stat-error-invalid-quoting-style", HashMap::from([("style".to_string(), style.clone())])))]
InvalidQuotingStyle { style: String },
#[error("{}", get_message("stat-error-missing-operand"))]
MissingOperand,
#[error("{}", get_message_with_args("stat-error-invalid-directive", HashMap::from([("directive".to_string(), directive.clone())])))]
InvalidDirective { directive: String },
#[error("{}", get_message_with_args("stat-error-cannot-read-filesystem", HashMap::from([("error".to_string(), error.clone())])))]
CannotReadFilesystem { error: String },
#[error("{}", get_message("stat-error-stdin-filesystem-mode"))]
StdinFilesystemMode,
#[error("{}", get_message_with_args("stat-error-cannot-read-filesystem-info", HashMap::from([("file".to_string(), file.clone()), ("error".to_string(), error.clone())])))]
CannotReadFilesystemInfo { file: String, error: String },
#[error("{}", get_message_with_args("stat-error-cannot-stat", HashMap::from([("file".to_string(), file.clone()), ("error".to_string(), error.clone())])))]
CannotStat { file: String, error: String },
}
impl UError for StatError {
fn code(&self) -> i32 {
1
}
}
mod options { mod options {
pub const DEREFERENCE: &str = "dereference"; pub const DEREFERENCE: &str = "dereference";
@ -54,7 +80,10 @@ fn check_bound(slice: &str, bound: usize, beg: usize, end: usize) -> UResult<()>
if end >= bound { if end >= bound {
return Err(USimpleError::new( return Err(USimpleError::new(
1, 1,
format!("{}: invalid directive", slice[beg..end].quote()), StatError::InvalidDirective {
directive: slice[beg..end].quote().to_string(),
}
.to_string(),
)); ));
} }
Ok(()) Ok(())
@ -104,7 +133,7 @@ enum QuotingStyle {
} }
impl std::str::FromStr for QuotingStyle { impl std::str::FromStr for QuotingStyle {
type Err = String; type Err = StatError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s { match s {
@ -112,7 +141,9 @@ impl std::str::FromStr for QuotingStyle {
"shell" => Ok(QuotingStyle::Shell), "shell" => Ok(QuotingStyle::Shell),
"shell-escape-always" => Ok(QuotingStyle::ShellEscapeAlways), "shell-escape-always" => Ok(QuotingStyle::ShellEscapeAlways),
// The others aren't exposed to the user // The others aren't exposed to the user
_ => Err(format!("Invalid quoting style: {s}")), _ => Err(StatError::InvalidQuotingStyle {
style: s.to_string(),
}),
} }
} }
} }
@ -144,24 +175,22 @@ trait ScanUtil {
} }
impl ScanUtil for str { impl ScanUtil for str {
/// Scans for a number at the beginning of the string
/// Returns the parsed number and the character count
/// Since we only deal with ASCII characters (+, -, 0-9), character count equals byte count
fn scan_num<F>(&self) -> Option<(F, usize)> fn scan_num<F>(&self) -> Option<(F, usize)>
where where
F: std::str::FromStr, F: std::str::FromStr,
{ {
let mut chars = self.chars(); let mut chars = self.chars();
let mut i = 0; let count = chars
match chars.next() { .next()
Some('-' | '+' | '0'..='9') => i += 1, .filter(|&c| c.is_ascii_digit() || c == '-' || c == '+')
_ => return None, .map(|_| 1 + chars.take_while(char::is_ascii_digit).count())
} .unwrap_or(0);
for c in chars {
match c { if count > 0 {
'0'..='9' => i += 1, F::from_str(&self[..count]).ok().map(|x| (x, count))
_ => break,
}
}
if i > 0 {
F::from_str(&self[..i]).ok().map(|x| (x, i))
} else { } else {
None None
} }
@ -607,6 +636,17 @@ impl Stater {
} }
} }
/// Converts a character index to a byte index in a UTF-8 string
/// This is necessary because Rust strings are UTF-8 encoded, so character positions
/// don't always align with byte positions for multi-byte characters
fn char_index_to_byte_index(format_str: &str, char_index: usize) -> usize {
format_str
.char_indices()
.nth(char_index)
.map(|(byte_idx, _)| byte_idx)
.unwrap_or(format_str.len())
}
fn handle_percent_case( fn handle_percent_case(
chars: &[char], chars: &[char],
i: &mut usize, i: &mut usize,
@ -632,7 +672,8 @@ impl Stater {
let mut precision = Precision::NotSpecified; let mut precision = Precision::NotSpecified;
let mut j = *i; let mut j = *i;
if let Some((field_width, offset)) = format_str[j..].scan_num::<usize>() { let j_byte = Self::char_index_to_byte_index(format_str, j);
if let Some((field_width, offset)) = format_str[j_byte..].scan_num::<usize>() {
width = field_width; width = field_width;
j += offset; j += offset;
@ -641,7 +682,10 @@ impl Stater {
let invalid_directive: String = chars[old..=j.min(bound - 1)].iter().collect(); let invalid_directive: String = chars[old..=j.min(bound - 1)].iter().collect();
return Err(USimpleError::new( return Err(USimpleError::new(
1, 1,
format!("{}: invalid directive", invalid_directive.quote()), StatError::InvalidDirective {
directive: invalid_directive.quote().to_string(),
}
.to_string(),
)); ));
} }
} }
@ -651,7 +695,8 @@ impl Stater {
j += 1; j += 1;
check_bound(format_str, bound, old, j)?; check_bound(format_str, bound, old, j)?;
match format_str[j..].scan_num::<i32>() { let j_byte = Self::char_index_to_byte_index(format_str, j);
match format_str[j_byte..].scan_num::<i32>() {
Some((value, offset)) => { Some((value, offset)) => {
if value >= 0 { if value >= 0 {
precision = Precision::Number(value as usize); precision = Precision::Number(value as usize);
@ -698,7 +743,7 @@ impl Stater {
) -> Token { ) -> Token {
*i += 1; *i += 1;
if *i >= bound { if *i >= bound {
show_warning!("backslash at end of format"); show_warning!("{}", get_message("stat-warning-backslash-end-format"));
return Token::Char('\\'); return Token::Char('\\');
} }
match chars[*i] { match chars[*i] {
@ -728,22 +773,30 @@ impl Stater {
Token::Byte(value) Token::Byte(value)
} }
'x' => { 'x' => {
// Parse hexadecimal escape sequence // Parse hexadecimal escape sequence (\xNN format)
// Uses UTF-8 safe byte indexing to handle multi-byte characters properly
if *i + 1 < bound { if *i + 1 < bound {
if let Some((c, offset)) = format_str[*i + 1..].scan_char(16) { let byte_index = Self::char_index_to_byte_index(format_str, *i + 1);
if let Some((c, offset)) = format_str[byte_index..].scan_char(16) {
*i += offset; *i += offset;
Token::Byte(c as u8) Token::Byte(c as u8)
} else { } else {
show_warning!("unrecognized escape '\\x'"); show_warning!("{}", get_message("stat-warning-unrecognized-escape-x"));
Token::Byte(b'x') Token::Byte(b'x')
} }
} else { } else {
show_warning!("incomplete hex escape '\\x'"); show_warning!("{}", get_message("stat-warning-incomplete-hex-escape"));
Token::Byte(b'x') Token::Byte(b'x')
} }
} }
other => { other => {
show_warning!("unrecognized escape '\\{other}'"); show_warning!(
"{}",
get_message_with_args(
"stat-warning-unrecognized-escape",
HashMap::from([("escape".to_string(), other.to_string())])
)
);
Token::Byte(other as u8) Token::Byte(other as u8)
} }
} }
@ -751,8 +804,8 @@ impl Stater {
fn generate_tokens(format_str: &str, use_printf: bool) -> UResult<Vec<Token>> { fn generate_tokens(format_str: &str, use_printf: bool) -> UResult<Vec<Token>> {
let mut tokens = Vec::new(); let mut tokens = Vec::new();
let bound = format_str.len();
let chars = format_str.chars().collect::<Vec<char>>(); let chars = format_str.chars().collect::<Vec<char>>();
let bound = chars.len();
let mut i = 0; let mut i = 0;
while i < bound { while i < bound {
match chars.get(i) { match chars.get(i) {
@ -785,10 +838,7 @@ impl Stater {
.map(|v| v.map(OsString::from).collect()) .map(|v| v.map(OsString::from).collect())
.unwrap_or_default(); .unwrap_or_default();
if files.is_empty() { if files.is_empty() {
return Err(Box::new(USimpleError { return Err(Box::new(StatError::MissingOperand) as Box<dyn UError>);
code: 1,
message: "missing operand\nTry 'stat --help' for more information.".to_string(),
}));
} }
let format_str = if matches.contains_id(options::PRINTF) { let format_str = if matches.contains_id(options::PRINTF) {
matches matches
@ -819,8 +869,13 @@ impl Stater {
} else { } else {
let mut mount_list = read_fs_list() let mut mount_list = read_fs_list()
.map_err(|e| { .map_err(|e| {
let context = "cannot read table of mounted file systems"; USimpleError::new(
USimpleError::new(e.code(), format!("{context}: {e}")) e.code(),
StatError::CannotReadFilesystem {
error: e.to_string(),
}
.to_string(),
)
})? })?
.iter() .iter()
.map(|mi| mi.mount_dir.clone()) .map(|mi| mi.mount_dir.clone())
@ -907,17 +962,17 @@ impl Stater {
match uucore::selinux::get_selinux_security_context(Path::new(file)) match uucore::selinux::get_selinux_security_context(Path::new(file))
{ {
Ok(ctx) => OutputType::Str(ctx), Ok(ctx) => OutputType::Str(ctx),
Err(_) => OutputType::Str( Err(_) => OutputType::Str(get_message(
"failed to get security context".to_string(), "stat-selinux-failed-get-context",
), )),
} }
} else { } else {
OutputType::Str("unsupported on this system".to_string()) OutputType::Str(get_message("stat-selinux-unsupported-system"))
} }
} }
#[cfg(not(feature = "selinux"))] #[cfg(not(feature = "selinux"))]
{ {
OutputType::Str("unsupported for this operating system".to_string()) OutputType::Str(get_message("stat-selinux-unsupported-os"))
} }
} }
// device number in decimal // device number in decimal
@ -1028,7 +1083,7 @@ impl Stater {
let display_name = file.to_string_lossy(); let display_name = file.to_string_lossy();
let file = if cfg!(unix) && display_name == "-" { let file = if cfg!(unix) && display_name == "-" {
if self.show_fs { if self.show_fs {
show_error!("using '-' to denote standard input does not work in file system mode"); show_error!("{}", StatError::StdinFilesystemMode);
return 1; return 1;
} }
if let Ok(p) = Path::new("/dev/stdin").canonicalize() { if let Ok(p) = Path::new("/dev/stdin").canonicalize() {
@ -1055,8 +1110,11 @@ impl Stater {
} }
Err(e) => { Err(e) => {
show_error!( show_error!(
"cannot read file system information for {}: {e}", "{}",
display_name.quote(), StatError::CannotReadFilesystemInfo {
file: display_name.quote().to_string(),
error: e.to_string()
}
); );
return 1; return 1;
} }
@ -1092,7 +1150,13 @@ impl Stater {
} }
} }
Err(e) => { Err(e) => {
show_error!("cannot stat {}: {e}", display_name.quote()); show_error!(
"{}",
StatError::CannotStat {
file: display_name.quote().to_string(),
error: e.to_string()
}
);
return 1; return 1;
} }
} }
@ -1107,25 +1171,64 @@ impl Stater {
if terse { if terse {
"%n %i %l %t %s %S %b %f %a %c %d\n".into() "%n %i %l %t %s %S %b %f %a %c %d\n".into()
} else { } else {
" File: \"%n\"\n ID: %-8i Namelen: %-7l Type: %T\nBlock \ format!(
size: %-10s Fundamental block size: %S\nBlocks: Total: %-10b \ " {}: \"%n\"\n {}: %-8i {}: %-7l {}: %T\n{} \
Free: %-10f Available: %a\nInodes: Total: %-10c Free: %d\n" {}: %-10s {} {}: %S\n{}: {}: %-10b \
.into() {}: %-10f {}: %a\n{}: {}: %-10c {}: %d\n",
get_message("stat-word-file"),
get_message("stat-word-id"),
get_message("stat-word-namelen"),
get_message("stat-word-type"),
get_message("stat-word-block"),
get_message("stat-word-size"),
get_message("stat-word-fundamental"),
get_message("stat-word-block-size"),
get_message("stat-word-blocks"),
get_message("stat-word-total"),
get_message("stat-word-free"),
get_message("stat-word-available"),
get_message("stat-word-inodes"),
get_message("stat-word-total"),
get_message("stat-word-free")
)
} }
} else if terse { } else if terse {
"%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %W %o\n".into() "%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %W %o\n".into()
} else { } else {
[ let device_line = if show_dev_type {
" File: %N\n Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n", format!(
if show_dev_type { "{}: %Dh/%dd\t{}: %-10i {}: %-5h {} {}: %t,%T\n",
"Device: %Dh/%dd\tInode: %-10i Links: %-5h Device type: %t,%T\n" get_message("stat-word-device"),
get_message("stat-word-inode"),
get_message("stat-word-links"),
get_message("stat-word-device"),
get_message("stat-word-type")
)
} else { } else {
"Device: %Dh/%dd\tInode: %-10i Links: %h\n" format!(
}, "{}: %Dh/%dd\t{}: %-10i {}: %h\n",
"Access: (%04a/%10.10A) Uid: (%5u/%8U) Gid: (%5g/%8G)\n", get_message("stat-word-device"),
"Access: %x\nModify: %y\nChange: %z\n Birth: %w\n", get_message("stat-word-inode"),
] get_message("stat-word-links")
.join("") )
};
format!(
" {}: %N\n {}: %-10s\t{}: %-10b {} {}: %-6o %F\n{}{}: (%04a/%10.10A) {}: (%5u/%8U) {}: (%5g/%8G)\n{}: %x\n{}: %y\n{}: %z\n {}: %w\n",
get_message("stat-word-file"),
get_message("stat-word-size"),
get_message("stat-word-blocks"),
get_message("stat-word-io"),
get_message("stat-word-block"),
device_line,
get_message("stat-word-access"),
get_message("stat-word-uid"),
get_message("stat-word-gid"),
get_message("stat-word-access"),
get_message("stat-word-modify"),
get_message("stat-word-change"),
get_message("stat-word-birth")
)
} }
} }
} }
@ -1155,42 +1258,35 @@ pub fn uu_app() -> Command {
Arg::new(options::DEREFERENCE) Arg::new(options::DEREFERENCE)
.short('L') .short('L')
.long(options::DEREFERENCE) .long(options::DEREFERENCE)
.help("follow links") .help(get_message("stat-help-dereference"))
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
) )
.arg( .arg(
Arg::new(options::FILE_SYSTEM) Arg::new(options::FILE_SYSTEM)
.short('f') .short('f')
.long(options::FILE_SYSTEM) .long(options::FILE_SYSTEM)
.help("display file system status instead of file status") .help(get_message("stat-help-file-system"))
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
) )
.arg( .arg(
Arg::new(options::TERSE) Arg::new(options::TERSE)
.short('t') .short('t')
.long(options::TERSE) .long(options::TERSE)
.help("print the information in terse form") .help(get_message("stat-help-terse"))
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
) )
.arg( .arg(
Arg::new(options::FORMAT) Arg::new(options::FORMAT)
.short('c') .short('c')
.long(options::FORMAT) .long(options::FORMAT)
.help( .help(get_message("stat-help-format"))
"use the specified FORMAT instead of the default;
output a newline after each use of FORMAT",
)
.value_name("FORMAT"), .value_name("FORMAT"),
) )
.arg( .arg(
Arg::new(options::PRINTF) Arg::new(options::PRINTF)
.long(options::PRINTF) .long(options::PRINTF)
.value_name("FORMAT") .value_name("FORMAT")
.help( .help(get_message("stat-help-printf")),
"like --format, but interpret backslash escapes,
and do not output a mandatory trailing newline;
if you want a newline, include \n in FORMAT",
),
) )
.arg( .arg(
Arg::new(options::FILES) Arg::new(options::FILES)