Skip to content

Commit afaff45

Browse files
authored
Merge branch 'main' into core-1
2 parents e265fa7 + a0a797d commit afaff45

18 files changed

Lines changed: 466 additions & 84 deletions

File tree

.vscode/cspell.dictionaries/jargon.wordlist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ semiprimes
136136
setcap
137137
setfacl
138138
setfattr
139+
SETFL
139140
setlocale
140141
shortcode
141142
shortcodes

build.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
55

6-
// spell-checker:ignore (vars) krate mangen
6+
// spell-checker:ignore (vars) krate mangen tldr
77

88
use std::env;
99
use std::fs::File;
@@ -19,6 +19,18 @@ pub fn main() {
1919
// See <https://doc.rust-lang.org/cargo/reference/build-scripts.html#change-detection>
2020
println!("cargo:rerun-if-changed=build.rs");
2121

22+
// Check for tldr.zip when building uudoc to warn users once at build time
23+
// instead of repeatedly at runtime for each utility
24+
if env::var("CARGO_FEATURE_UUDOC").is_ok() && !Path::new("docs/tldr.zip").exists() {
25+
println!(
26+
"cargo:warning=No tldr archive found, so the documentation will not include examples."
27+
);
28+
println!("cargo:warning=To include examples, download the tldr archive:");
29+
println!(
30+
"cargo:warning= curl -L https://github.com/tldr-pages/tldr/releases/latest/download/tldr.zip -o docs/tldr.zip"
31+
);
32+
}
33+
2234
if let Ok(profile) = env::var("PROFILE") {
2335
println!("cargo:rustc-cfg=build={profile:?}");
2436
}

src/bin/uudoc.rs

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -133,19 +133,6 @@ fn gen_completions<T: Args>(args: impl Iterator<Item = OsString>, util_map: &Uti
133133
process::exit(0);
134134
}
135135

136-
/// print tldr error
137-
fn print_tldr_error() {
138-
eprintln!("Warning: No tldr archive found, so the documentation will not include examples.");
139-
eprintln!(
140-
"To include examples in the documentation, download the tldr archive and put it in the docs/ folder."
141-
);
142-
eprintln!();
143-
eprintln!(
144-
" curl -L https://github.com/tldr-pages/tldr/releases/latest/download/tldr.zip -o docs/tldr.zip"
145-
);
146-
eprintln!();
147-
}
148-
149136
/// # Errors
150137
/// Returns an error if the writer fails.
151138
#[allow(clippy::too_many_lines)]
@@ -162,9 +149,6 @@ fn main() -> io::Result<()> {
162149
match command {
163150
"manpage" => {
164151
let args_iter = args.into_iter().skip(2);
165-
if tldr_zip.is_none() {
166-
print_tldr_error();
167-
}
168152
gen_manpage(
169153
&mut tldr_zip,
170154
args_iter,
@@ -186,9 +170,6 @@ fn main() -> io::Result<()> {
186170
}
187171
}
188172
}
189-
if tldr_zip.is_none() {
190-
print_tldr_error();
191-
}
192173
let utils = util_map::<Box<dyn Iterator<Item = OsString>>>();
193174
match std::fs::create_dir("docs/src/utils/") {
194175
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => Ok(()),

src/uu/numfmt/locales/en-US.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ numfmt-error-invalid-header = invalid header value { $value }
5959
numfmt-error-grouping-cannot-be-combined-with-to = grouping cannot be combined with --to
6060
numfmt-error-delimiter-must-be-single-character = the delimiter must be a single character
6161
numfmt-error-invalid-number-empty = invalid number: ''
62+
numfmt-error-invalid-specific-suffix = invalid suffix in input { $input }: { $suffix }
6263
numfmt-error-invalid-suffix = invalid suffix in input: { $input }
6364
numfmt-error-invalid-number = invalid number: { $input }
6465
numfmt-error-missing-i-suffix = missing 'i' suffix in input: '{ $number }{ $suffix }' (e.g Ki/Mi/Gi)

src/uu/numfmt/locales/fr-FR.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ numfmt-error-grouping-cannot-be-combined-with-to = le groupement ne peut pas êt
5959
numfmt-error-delimiter-must-be-single-character = le délimiteur doit être un seul caractère
6060
numfmt-error-invalid-number-empty = nombre invalide : ''
6161
numfmt-error-invalid-suffix = suffixe invalide dans l'entrée : { $input }
62+
numfmt-error-invalid-specific-suffix = suffixe invalide dans l'entrée { $input } : { $suffix }
6263
numfmt-error-invalid-number = nombre invalide : { $input }
6364
numfmt-error-missing-i-suffix = suffixe 'i' manquant dans l'entrée : '{ $number }{ $suffix }' (par ex. Ki/Mi/Gi)
6465
numfmt-error-rejecting-suffix = rejet du suffixe dans l'entrée : '{ $number }{ $suffix }' (considérez utiliser --from)

src/uu/numfmt/src/format.rs

Lines changed: 184 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,97 @@ impl<'a> Iterator for WhitespaceSplitter<'a> {
6262
}
6363
}
6464

65-
fn parse_suffix(s: &str) -> Result<(f64, Option<Suffix>)> {
65+
fn find_numeric_beginning(s: &str) -> Option<&str> {
66+
let mut decimal_point_seen = false;
67+
if s.is_empty() {
68+
return None;
69+
}
70+
71+
for (idx, c) in s.char_indices() {
72+
if c == '-' && idx == 0 {
73+
continue;
74+
}
75+
if c.is_ascii_digit() {
76+
continue;
77+
}
78+
if c == '.' && !decimal_point_seen {
79+
decimal_point_seen = true;
80+
continue;
81+
}
82+
if s[..idx].parse::<f64>().is_err() {
83+
return None;
84+
}
85+
return Some(&s[..idx]);
86+
}
87+
88+
Some(s)
89+
}
90+
91+
// finds the valid beginning part of an input string, or None.
92+
fn find_valid_number_with_suffix<'a>(s: &'a str, unit: &Unit) -> Option<&'a str> {
93+
let numeric_part = find_numeric_beginning(s)?;
94+
95+
let accepts_suffix = unit != &Unit::None;
96+
let accepts_i = [Unit::Auto, Unit::Iec(true)].contains(unit);
97+
98+
let mut characters = s.chars().skip(numeric_part.len());
99+
let potential_suffix = characters.next();
100+
let potential_i = characters.next();
101+
102+
if !accepts_suffix {
103+
return Some(numeric_part);
104+
}
105+
106+
match (potential_suffix, potential_i) {
107+
(Some(suffix), None) if RawSuffix::try_from(&suffix).is_ok() => {
108+
Some(&s[..=numeric_part.len()])
109+
}
110+
(Some(suffix), Some('i')) if accepts_i && RawSuffix::try_from(&suffix).is_ok() => {
111+
Some(&s[..numeric_part.len() + 2])
112+
}
113+
(Some(suffix), Some(_)) if RawSuffix::try_from(&suffix).is_ok() => {
114+
Some(&s[..=numeric_part.len()])
115+
}
116+
_ => Some(numeric_part),
117+
}
118+
}
119+
120+
fn detailed_error_message(s: &str, unit: &Unit) -> Option<String> {
121+
if s.is_empty() {
122+
return Some(translate!("numfmt-error-invalid-number-empty"));
123+
}
124+
125+
let valid_part = find_valid_number_with_suffix(s, unit)
126+
.ok_or(translate!("numfmt-error-invalid-number", "input" => s.quote()))
127+
.ok()?;
128+
129+
if valid_part != s && valid_part.parse::<f64>().is_ok() {
130+
return match s.chars().nth(valid_part.len()) {
131+
Some(v) if RawSuffix::try_from(&v).is_ok() => Some(
132+
translate!("numfmt-error-rejecting-suffix", "number" => valid_part, "suffix" => s[valid_part.len()..]),
133+
),
134+
135+
_ => Some(translate!("numfmt-error-invalid-suffix", "input" => s.quote())),
136+
};
137+
}
138+
139+
if valid_part != s && valid_part.parse::<f64>().is_err() {
140+
return Some(
141+
translate!("numfmt-error-invalid-specific-suffix", "input" => s.quote(), "suffix" => s[valid_part.len()..].quote()),
142+
);
143+
}
144+
None
145+
}
146+
147+
fn parse_suffix(s: &str, unit: &Unit) -> Result<(f64, Option<Suffix>)> {
66148
if s.is_empty() {
67149
return Err(translate!("numfmt-error-invalid-number-empty"));
68150
}
69151

70152
let with_i = s.ends_with('i');
153+
if with_i && ![Unit::Auto, Unit::Iec(true)].contains(unit) {
154+
return Err(translate!("numfmt-error-invalid-suffix", "input" => s.quote()));
155+
}
71156
let mut iter = s.chars();
72157
if with_i {
73158
iter.next_back();
@@ -86,17 +171,7 @@ fn parse_suffix(s: &str) -> Result<(f64, Option<Suffix>)> {
86171
Some('Q') => Some((RawSuffix::Q, with_i)),
87172
Some('0'..='9') if !with_i => None,
88173
_ => {
89-
// If with_i is true, the string ends with 'i' but there's no valid suffix letter
90-
// This is always an invalid suffix (e.g., "1i", "2Ai")
91-
if with_i {
92-
return Err(translate!("numfmt-error-invalid-suffix", "input" => s.quote()));
93-
}
94-
// For other cases, check if the number part (without the last character) is valid
95-
let number_part = &s[..s.len() - 1];
96-
if number_part.is_empty() || number_part.parse::<f64>().is_err() {
97-
return Err(translate!("numfmt-error-invalid-number", "input" => s.quote()));
98-
}
99-
return Err(translate!("numfmt-error-invalid-suffix", "input" => s.quote()));
174+
return Err(translate!("numfmt-error-invalid-number", "input" => s.quote()));
100175
}
101176
};
102177

@@ -164,7 +239,8 @@ fn remove_suffix(i: f64, s: Option<Suffix>, u: &Unit) -> Result<f64> {
164239
}
165240

166241
fn transform_from(s: &str, opts: &TransformOptions) -> Result<f64> {
167-
let (i, suffix) = parse_suffix(s)?;
242+
let (i, suffix) = parse_suffix(s, &opts.from)
243+
.map_err(|original| detailed_error_message(s, &opts.from).unwrap_or(original))?;
168244
let i = i * (opts.from_unit as f64);
169245

170246
remove_suffix(i, suffix, &opts.from).map(|n| {
@@ -491,7 +567,7 @@ mod tests {
491567

492568
#[test]
493569
fn test_parse_suffix_q_r_k() {
494-
let result = parse_suffix("1Q");
570+
let result = parse_suffix("1Q", &Unit::Auto);
495571
assert!(result.is_ok());
496572
let (number, suffix) = result.unwrap();
497573
assert_eq!(number, 1.0);
@@ -500,7 +576,7 @@ mod tests {
500576
assert_eq!(raw_suffix as i32, RawSuffix::Q as i32);
501577
assert!(!with_i);
502578

503-
let result = parse_suffix("2R");
579+
let result = parse_suffix("2R", &Unit::Auto);
504580
assert!(result.is_ok());
505581
let (number, suffix) = result.unwrap();
506582
assert_eq!(number, 2.0);
@@ -509,7 +585,7 @@ mod tests {
509585
assert_eq!(raw_suffix as i32, RawSuffix::R as i32);
510586
assert!(!with_i);
511587

512-
let result = parse_suffix("3k");
588+
let result = parse_suffix("3k", &Unit::Auto);
513589
assert!(result.is_ok());
514590
let (number, suffix) = result.unwrap();
515591
assert_eq!(number, 3.0);
@@ -518,7 +594,7 @@ mod tests {
518594
assert_eq!(raw_suffix as i32, RawSuffix::K as i32);
519595
assert!(!with_i);
520596

521-
let result = parse_suffix("4Qi");
597+
let result = parse_suffix("4Qi", &Unit::Auto);
522598
assert!(result.is_ok());
523599
let (number, suffix) = result.unwrap();
524600
assert_eq!(number, 4.0);
@@ -527,7 +603,7 @@ mod tests {
527603
assert_eq!(raw_suffix as i32, RawSuffix::Q as i32);
528604
assert!(with_i);
529605

530-
let result = parse_suffix("5Ri");
606+
let result = parse_suffix("5Ri", &Unit::Auto);
531607
assert!(result.is_ok());
532608
let (number, suffix) = result.unwrap();
533609
assert_eq!(number, 5.0);
@@ -539,22 +615,41 @@ mod tests {
539615

540616
#[test]
541617
fn test_parse_suffix_error_messages() {
542-
let result = parse_suffix("foo");
618+
let result = parse_suffix("foo", &Unit::Auto);
543619
assert!(result.is_err());
544620
let error = result.unwrap_err();
545621
assert!(error.contains("numfmt-error-invalid-number") || error.contains("invalid number"));
546622
assert!(!error.contains("invalid suffix"));
547623

548-
let result = parse_suffix("World");
624+
let result = parse_suffix("World", &Unit::Auto);
549625
assert!(result.is_err());
550626
let error = result.unwrap_err();
551627
assert!(error.contains("numfmt-error-invalid-number") || error.contains("invalid number"));
552628
assert!(!error.contains("invalid suffix"));
629+
}
553630

554-
let result = parse_suffix("123i");
555-
assert!(result.is_err());
556-
let error = result.unwrap_err();
631+
#[test]
632+
fn test_detailed_error_message() {
633+
let result = detailed_error_message("123i", &Unit::Auto);
634+
assert!(result.is_some());
635+
let error = result.unwrap();
557636
assert!(error.contains("numfmt-error-invalid-suffix") || error.contains("invalid suffix"));
637+
638+
let result = detailed_error_message("5MF", &Unit::Auto);
639+
assert!(result.is_some());
640+
let error = result.unwrap();
641+
assert!(
642+
error.contains("numfmt-error-invalid-specific-suffix")
643+
|| error.contains("invalid suffix")
644+
);
645+
646+
let result = detailed_error_message("5KM", &Unit::Auto);
647+
assert!(result.is_some());
648+
let error = result.unwrap();
649+
assert!(
650+
error.contains("numfmt-error-invalid-specific-suffix")
651+
|| error.contains("invalid suffix")
652+
);
558653
}
559654

560655
#[test]
@@ -578,6 +673,72 @@ mod tests {
578673
assert_eq!(result.unwrap(), IEC_BASES[9]);
579674
}
580675

676+
#[test]
677+
fn test_find_valid_part() {
678+
assert_eq!(
679+
find_valid_number_with_suffix("12345KL", &Unit::Auto),
680+
Some("12345K")
681+
);
682+
assert_eq!(
683+
find_valid_number_with_suffix("12345K", &Unit::Auto),
684+
Some("12345K")
685+
);
686+
assert_eq!(
687+
find_valid_number_with_suffix("12345", &Unit::Auto),
688+
Some("12345")
689+
);
690+
assert_eq!(
691+
find_valid_number_with_suffix("asd12345KL", &Unit::Auto),
692+
None
693+
);
694+
assert_eq!(
695+
find_valid_number_with_suffix("8asdf", &Unit::Auto),
696+
Some("8")
697+
);
698+
assert_eq!(find_valid_number_with_suffix("5i", &Unit::Si), Some("5"));
699+
assert_eq!(
700+
find_valid_number_with_suffix("5i", &Unit::Iec(true)),
701+
Some("5")
702+
);
703+
assert_eq!(
704+
find_valid_number_with_suffix("0.1KL", &Unit::Auto),
705+
Some("0.1K")
706+
);
707+
assert_eq!(
708+
find_valid_number_with_suffix("0.1", &Unit::Auto),
709+
Some("0.1")
710+
);
711+
assert_eq!(
712+
find_valid_number_with_suffix("-0.1MT", &Unit::Auto),
713+
Some("-0.1M")
714+
);
715+
assert_eq!(
716+
find_valid_number_with_suffix("-0.1PT", &Unit::Auto),
717+
Some("-0.1P")
718+
);
719+
assert_eq!(
720+
find_valid_number_with_suffix("-0.1PT", &Unit::Auto),
721+
Some("-0.1P")
722+
);
723+
assert_eq!(
724+
find_valid_number_with_suffix("123.4.5", &Unit::Auto),
725+
Some("123.4")
726+
);
727+
assert_eq!(
728+
find_valid_number_with_suffix("0.55KiJ", &Unit::Iec(true)),
729+
Some("0.55Ki")
730+
);
731+
assert_eq!(
732+
find_valid_number_with_suffix("0.55KiJ", &Unit::Iec(false)),
733+
Some("0.55K")
734+
);
735+
assert_eq!(
736+
find_valid_number_with_suffix("123KICK", &Unit::Auto),
737+
Some("123K")
738+
);
739+
assert_eq!(find_valid_number_with_suffix("", &Unit::Auto), None);
740+
}
741+
581742
#[test]
582743
fn test_consider_suffix_q_r() {
583744
use crate::options::RoundMethod;

0 commit comments

Comments
 (0)