Skip to main content

uucore/
lib.rs

1// This file is part of the uutils coreutils package.
2//
3// For the full copyright and license information, please view the LICENSE
4// file that was distributed with this source code.
5//! library ~ (core/bundler file)
6// #![deny(missing_docs)] //TODO: enable this
7//
8// spell-checker:ignore sigaction SIGBUS SIGSEGV extendedbigdecimal myutil logind
9
10// * feature-gated external crates (re-shared as public internal modules)
11#[cfg(feature = "libc")]
12pub extern crate libc;
13#[cfg(all(feature = "windows-sys", target_os = "windows"))]
14pub extern crate windows_sys;
15
16//## internal modules
17
18mod features; // feature-gated code modules
19mod macros; // crate macros (macro_rules-type; exported to `crate::...`)
20mod mods; // core cross-platform modules
21
22pub use uucore_procs::*;
23
24// * cross-platform modules
25pub use crate::mods::clap_localization;
26pub use crate::mods::display;
27pub use crate::mods::error;
28#[cfg(feature = "fs")]
29pub use crate::mods::io;
30pub use crate::mods::line_ending;
31pub use crate::mods::locale;
32pub use crate::mods::os;
33pub use crate::mods::panic;
34pub use crate::mods::posix;
35
36// * feature-gated modules
37#[cfg(feature = "backup-control")]
38pub use crate::features::backup_control;
39#[cfg(feature = "benchmark")]
40pub use crate::features::benchmark;
41#[cfg(feature = "buf-copy")]
42pub use crate::features::buf_copy;
43#[cfg(feature = "checksum")]
44pub use crate::features::checksum;
45#[cfg(feature = "colors")]
46pub use crate::features::colors;
47#[cfg(feature = "encoding")]
48pub use crate::features::encoding;
49#[cfg(feature = "extendedbigdecimal")]
50pub use crate::features::extendedbigdecimal;
51#[cfg(feature = "fast-inc")]
52pub use crate::features::fast_inc;
53#[cfg(feature = "format")]
54pub use crate::features::format;
55#[cfg(feature = "fs")]
56pub use crate::features::fs;
57#[cfg(feature = "hardware")]
58pub use crate::features::hardware;
59#[cfg(feature = "i18n-common")]
60pub use crate::features::i18n;
61#[cfg(feature = "lines")]
62pub use crate::features::lines;
63#[cfg(any(
64    feature = "parser",
65    feature = "parser-num",
66    feature = "parser-size",
67    feature = "parser-glob"
68))]
69pub use crate::features::parser;
70#[cfg(feature = "quoting-style")]
71pub use crate::features::quoting_style;
72#[cfg(feature = "ranges")]
73pub use crate::features::ranges;
74#[cfg(feature = "ringbuffer")]
75pub use crate::features::ringbuffer;
76#[cfg(feature = "sum")]
77pub use crate::features::sum;
78#[cfg(feature = "feat_systemd_logind")]
79pub use crate::features::systemd_logind;
80#[cfg(feature = "time")]
81pub use crate::features::time;
82#[cfg(feature = "update-control")]
83pub use crate::features::update_control;
84#[cfg(feature = "uptime")]
85pub use crate::features::uptime;
86#[cfg(feature = "version-cmp")]
87pub use crate::features::version_cmp;
88
89// * (platform-specific) feature-gated modules
90// ** non-windows (i.e. Unix + Fuchsia)
91#[cfg(all(not(windows), feature = "mode"))]
92pub use crate::features::mode;
93// ** unix-only
94#[cfg(all(unix, feature = "entries"))]
95pub use crate::features::entries;
96#[cfg(all(unix, feature = "perms"))]
97pub use crate::features::perms;
98#[cfg(all(unix, any(feature = "pipes", feature = "buf-copy")))]
99pub use crate::features::pipes;
100#[cfg(all(unix, feature = "process"))]
101pub use crate::features::process;
102#[cfg(all(unix, not(target_os = "redox")))]
103pub use crate::features::safe_traversal;
104#[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))]
105pub use crate::features::signals;
106#[cfg(all(
107    unix,
108    not(target_os = "android"),
109    not(target_os = "fuchsia"),
110    not(target_os = "openbsd"),
111    not(target_os = "redox"),
112    feature = "utmpx"
113))]
114pub use crate::features::utmpx;
115// ** windows-only
116#[cfg(all(windows, feature = "wide"))]
117pub use crate::features::wide;
118
119#[cfg(feature = "fsext")]
120pub use crate::features::fsext;
121
122#[cfg(all(unix, feature = "fsxattr"))]
123pub use crate::features::fsxattr;
124
125#[cfg(all(feature = "selinux", any(target_os = "linux", target_os = "android")))]
126pub use crate::features::selinux;
127
128#[cfg(all(target_os = "linux", feature = "smack"))]
129pub use crate::features::smack;
130
131//## core functions
132
133#[cfg(unix)]
134use nix::errno::Errno;
135#[cfg(unix)]
136use nix::sys::signal::{
137    SaFlags, SigAction, SigHandler::SigDfl, SigSet, Signal::SIGBUS, Signal::SIGSEGV, sigaction,
138};
139use std::borrow::Cow;
140use std::ffi::{OsStr, OsString};
141use std::io::{BufRead, BufReader};
142use std::iter;
143#[cfg(unix)]
144use std::os::unix::ffi::{OsStrExt, OsStringExt};
145#[cfg(target_os = "wasi")]
146use std::os::wasi::ffi::{OsStrExt, OsStringExt};
147use std::str;
148use std::str::Utf8Chunk;
149use std::sync::{LazyLock, atomic::Ordering};
150
151/// Disables the custom signal handlers installed by Rust for stack-overflow handling. With those custom signal handlers processes ignore the first SIGBUS and SIGSEGV signal they receive.
152/// See <https://github.com/rust-lang/rust/blob/8ac1525e091d3db28e67adcbbd6db1e1deaa37fb/src/libstd/sys/unix/stack_overflow.rs#L71-L92> for details.
153#[cfg(unix)]
154pub fn disable_rust_signal_handlers() -> Result<(), Errno> {
155    unsafe {
156        sigaction(
157            SIGSEGV,
158            &SigAction::new(SigDfl, SaFlags::empty(), SigSet::all()),
159        )
160    }?;
161    unsafe {
162        sigaction(
163            SIGBUS,
164            &SigAction::new(SigDfl, SaFlags::empty(), SigSet::all()),
165        )
166    }?;
167    Ok(())
168}
169
170pub fn get_canonical_util_name(util_name: &str) -> &str {
171    // remove the "uu_" prefix
172    let util_name = &util_name[3..];
173    match util_name {
174        // uu_test aliases - '[' is an alias for test
175        "[" => "test",
176        "dir" => "ls",  // dir is an alias for ls
177        "vdir" => "ls", // vdir is an alias for ls
178
179        // Default case - return the util name as is
180        _ => util_name,
181    }
182}
183
184/// Execute utility code for `util`.
185///
186/// This macro expands to a main function that invokes the `uumain` function in `util`
187/// Exits with code returned by `uumain`.
188#[macro_export]
189macro_rules! bin {
190    ($util:ident) => {
191        pub fn main() {
192            use std::io::Write;
193            use uucore::locale;
194
195            // Preserve inherited SIGPIPE settings (e.g., from env --default-signal=PIPE)
196            uucore::panic::preserve_inherited_sigpipe();
197
198            // suppress extraneous error output for SIGPIPE failures/panics
199            uucore::panic::mute_sigpipe_panic();
200            locale::setup_localization(uucore::get_canonical_util_name(stringify!($util)))
201                .unwrap_or_else(|err| {
202                    match err {
203                        uucore::locale::LocalizationError::ParseResource {
204                            error: err_msg,
205                            snippet,
206                        } => eprintln!("Localization parse error at {snippet}: {err_msg:?}"),
207                        other => eprintln!("Could not init the localization system: {other}"),
208                    }
209                    std::process::exit(99)
210                });
211
212            // execute utility code
213            let code = $util::uumain(uucore::args_os());
214            // (defensively) flush stdout for utility prior to exit; see <https://github.com/rust-lang/rust/issues/23818>
215            if let Err(e) = std::io::stdout().flush() {
216                eprintln!("Error flushing stdout: {e}");
217            }
218
219            std::process::exit(code);
220        }
221    };
222}
223
224/// Generate the version string for clap.
225///
226/// The generated string has the format `(<project name>) <version>`, for
227/// example: "(uutils coreutils) 0.30.0". clap will then prefix it with the util name.
228#[macro_export]
229macro_rules! crate_version {
230    () => {
231        concat!("(uutils coreutils) ", env!("CARGO_PKG_VERSION"))
232    };
233}
234
235/// Generate the usage string for clap.
236///
237/// This function does two things. It indents all but the first line to align
238/// the lines because clap adds "Usage: " to the first line. And it replaces
239/// all occurrences of `{}` with the execution phrase and returns the resulting
240/// `String`. It does **not** support more advanced formatting features such
241/// as `{0}`.
242pub fn format_usage(s: &str) -> String {
243    let s = s.replace('\n', &format!("\n{}", " ".repeat(7)));
244    s.replace("{}", execution_phrase())
245}
246
247/// Creates a localized help template for clap commands.
248///
249/// This function returns a help template that uses the localized
250/// "Usage:" label from the translation files. This ensures consistent
251/// localization across all utilities.
252///
253/// Note: We avoid using clap's `{usage-heading}` placeholder because it is
254/// hardcoded to "Usage:" and cannot be localized. Instead, we manually
255/// construct the usage line with the localized label.
256///
257/// # Parameters
258/// - `util_name`: The name of the utility (for localization setup)
259///
260/// # Example
261/// ```no_run
262/// use clap::Command;
263/// use uucore::localized_help_template;
264///
265/// let app = Command::new("myutil")
266///     .help_template(localized_help_template("myutil"));
267/// ```
268pub fn localized_help_template(util_name: &str) -> clap::builder::StyledStr {
269    use std::io::IsTerminal;
270
271    // Determine if colors should be enabled - same logic as configure_localized_command
272    let colors_enabled = if std::env::var("NO_COLOR").is_ok() {
273        false
274    } else if std::env::var("CLICOLOR_FORCE").is_ok() || std::env::var("FORCE_COLOR").is_ok() {
275        true
276    } else {
277        IsTerminal::is_terminal(&std::io::stdout())
278            && std::env::var("TERM").unwrap_or_default() != "dumb"
279    };
280
281    localized_help_template_with_colors(util_name, colors_enabled)
282}
283
284/// Create a localized help template with explicit color control
285/// This ensures color detection consistency between clap and our template
286pub fn localized_help_template_with_colors(
287    util_name: &str,
288    colors_enabled: bool,
289) -> clap::builder::StyledStr {
290    use std::fmt::Write;
291
292    // Ensure localization is initialized for this utility
293    let _ = locale::setup_localization(util_name);
294
295    // Get the localized "Usage" label
296    let usage_label = crate::locale::translate!("common-usage");
297
298    // Create a styled template
299    let mut template = clap::builder::StyledStr::new();
300
301    // Add the basic template parts
302    writeln!(template, "{{before-help}}{{about-with-newline}}").unwrap();
303
304    // Add styled usage header (bold + underline like clap's default)
305    if colors_enabled {
306        write!(
307            template,
308            "\x1b[1m\x1b[4m{usage_label}:\x1b[0m {{usage}}\n\n"
309        )
310        .unwrap();
311    } else {
312        write!(template, "{usage_label}: {{usage}}\n\n").unwrap();
313    }
314
315    // Add the rest
316    write!(template, "{{all-args}}{{after-help}}").unwrap();
317
318    template
319}
320
321/// Used to check if the utility is the second argument.
322/// Used to check if we were called as a multicall binary (`coreutils <utility>`)
323pub fn get_utility_is_second_arg() -> bool {
324    macros::UTILITY_IS_SECOND_ARG.load(Ordering::SeqCst)
325}
326
327/// Change the value of `UTILITY_IS_SECOND_ARG` to true
328/// Used to specify that the utility is the second argument.
329pub fn set_utility_is_second_arg() {
330    macros::UTILITY_IS_SECOND_ARG.store(true, Ordering::SeqCst);
331}
332
333// args_os() can be expensive to call, it copies all of argv before iterating.
334// So if we want only the first arg or so it's overkill. We cache it.
335#[cfg(windows)]
336static ARGV: LazyLock<Vec<OsString>> = LazyLock::new(|| wild::args_os().collect());
337#[cfg(not(windows))]
338static ARGV: LazyLock<Vec<OsString>> = LazyLock::new(|| std::env::args_os().collect());
339
340static UTIL_NAME: LazyLock<String> = LazyLock::new(|| {
341    let base_index = usize::from(get_utility_is_second_arg());
342    let is_man = usize::from(ARGV[base_index].eq("manpage"));
343    let argv_index = base_index + is_man;
344
345    // Strip directory path to show only utility name
346    // (e.g., "mkdir" instead of "./target/debug/mkdir")
347    // in version output, error messages, and other user-facing output
348    std::path::Path::new(&ARGV[argv_index])
349        .file_name()
350        .unwrap_or(&ARGV[argv_index])
351        .to_string_lossy()
352        .into_owned()
353});
354
355/// Derive the utility name.
356pub fn util_name() -> &'static str {
357    &UTIL_NAME
358}
359
360static EXECUTION_PHRASE: LazyLock<String> = LazyLock::new(|| {
361    if get_utility_is_second_arg() {
362        ARGV.iter()
363            .take(2)
364            .map(|os_str| os_str.to_string_lossy().into_owned())
365            .collect::<Vec<_>>()
366            .join(" ")
367    } else {
368        ARGV[0].to_string_lossy().into_owned()
369    }
370});
371
372/// Derive the complete execution phrase for "usage".
373pub fn execution_phrase() -> &'static str {
374    &EXECUTION_PHRASE
375}
376
377/// Args contains arguments passed to the utility.
378/// It is a trait that extends `Iterator<Item = OsString>`.
379/// It provides utility functions to collect the arguments into a `Vec<String>`.
380/// The collected `Vec<String>` can be lossy or ignore invalid encoding.
381pub trait Args: Iterator<Item = OsString> + Sized {
382    /// Collects the iterator into a `Vec<String>`, lossily converting the `OsString`s to `Strings`.
383    fn collect_lossy(self) -> Vec<String> {
384        self.map(|s| s.to_string_lossy().into_owned()).collect()
385    }
386
387    /// Collects the iterator into a `Vec<String>`, removing any elements that contain invalid encoding.
388    fn collect_ignore(self) -> Vec<String> {
389        self.filter_map(|s| s.into_string().ok()).collect()
390    }
391}
392
393impl<T: Iterator<Item = OsString> + Sized> Args for T {}
394
395/// Returns an iterator over the command line arguments as `OsString`s.
396/// args_os() can be expensive to call
397pub fn args_os() -> impl Iterator<Item = OsString> {
398    ARGV.iter().cloned()
399}
400
401/// Returns an iterator over the command line arguments as `OsString`s, filtering out empty arguments.
402/// This is useful for handling cases where extra whitespace or empty arguments are present.
403/// args_os_filtered() can be expensive to call
404pub fn args_os_filtered() -> impl Iterator<Item = OsString> {
405    ARGV.iter().filter(|arg| !arg.is_empty()).cloned()
406}
407
408/// Read a line from stdin and check whether the first character is `'y'` or `'Y'`
409pub fn read_yes() -> bool {
410    let mut s = String::new();
411    match std::io::stdin().read_line(&mut s) {
412        Ok(_) => matches!(s.chars().next(), Some('y' | 'Y')),
413        _ => false,
414    }
415}
416
417#[derive(Debug)]
418pub struct NonUtf8OsStrError {
419    input_lossy_string: String,
420}
421
422impl std::fmt::Display for NonUtf8OsStrError {
423    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
424        use os_display::Quotable;
425        let quoted = self.input_lossy_string.quote();
426        f.write_fmt(format_args!(
427            "invalid UTF-8 input {quoted} encountered when converting to bytes on a platform that doesn't expose byte arguments",
428        ))
429    }
430}
431
432impl std::error::Error for NonUtf8OsStrError {}
433impl error::UError for NonUtf8OsStrError {}
434
435/// Converts an `OsStr` to a UTF-8 `&[u8]`.
436///
437/// This always succeeds on unix platforms,
438/// and fails on other platforms if the string can't be coerced to UTF-8.
439#[cfg_attr(any(unix, target_os = "wasi"), expect(clippy::unnecessary_wraps))]
440pub fn os_str_as_bytes(os_string: &OsStr) -> Result<&[u8], NonUtf8OsStrError> {
441    #[cfg(any(unix, target_os = "wasi"))]
442    return Ok(os_string.as_bytes());
443
444    #[cfg(not(any(unix, target_os = "wasi")))]
445    os_string
446        .to_str()
447        .ok_or_else(|| NonUtf8OsStrError {
448            input_lossy_string: os_string.to_string_lossy().into_owned(),
449        })
450        .map(str::as_bytes)
451}
452
453/// Performs a potentially lossy conversion from `OsStr` to UTF-8 bytes.
454///
455/// This is always lossless on unix platforms,
456/// and wraps [`OsStr::to_string_lossy`] on non-unix platforms.
457pub fn os_str_as_bytes_lossy(os_string: &OsStr) -> Cow<'_, [u8]> {
458    #[cfg(any(unix, target_os = "wasi"))]
459    return Cow::from(os_string.as_bytes());
460
461    #[cfg(not(any(unix, target_os = "wasi")))]
462    match os_string.to_string_lossy() {
463        Cow::Borrowed(slice) => Cow::from(slice.as_bytes()),
464        Cow::Owned(owned) => Cow::from(owned.into_bytes()),
465    }
466}
467
468/// Converts a `&[u8]` to an `&OsStr`,
469/// or parses it as UTF-8 into an [`OsString`] on non-unix platforms.
470///
471/// This always succeeds on unix platforms,
472/// and fails on other platforms if the bytes can't be parsed as UTF-8.
473#[cfg_attr(any(unix, target_os = "wasi"), expect(clippy::unnecessary_wraps))]
474pub fn os_str_from_bytes(bytes: &[u8]) -> error::UResult<Cow<'_, OsStr>> {
475    #[cfg(any(unix, target_os = "wasi"))]
476    return Ok(Cow::Borrowed(OsStr::from_bytes(bytes)));
477
478    #[cfg(not(any(unix, target_os = "wasi")))]
479    Ok(Cow::Owned(OsString::from(str::from_utf8(bytes).map_err(
480        |_| error::UUsageError::new(1, "Unable to transform bytes into OsStr"),
481    )?)))
482}
483
484/// Converts a `Vec<u8>` into an `OsString`, parsing as UTF-8 on non-unix platforms.
485///
486/// This always succeeds on unix platforms,
487/// and fails on other platforms if the bytes can't be parsed as UTF-8.
488#[cfg_attr(any(unix, target_os = "wasi"), expect(clippy::unnecessary_wraps))]
489pub fn os_string_from_vec(vec: Vec<u8>) -> error::UResult<OsString> {
490    #[cfg(any(unix, target_os = "wasi"))]
491    return Ok(OsString::from_vec(vec));
492
493    #[cfg(not(any(unix, target_os = "wasi")))]
494    Ok(OsString::from(String::from_utf8(vec).map_err(|_| {
495        error::UUsageError::new(1, "invalid UTF-8 was detected in one or more arguments")
496    })?))
497}
498
499/// Converts an `OsString` into a `Vec<u8>`, parsing as UTF-8 on non-unix platforms.
500///
501/// This always succeeds on unix platforms,
502/// and fails on other platforms if the bytes can't be parsed as UTF-8.
503#[cfg_attr(any(unix, target_os = "wasi"), expect(clippy::unnecessary_wraps))]
504pub fn os_string_to_vec(s: OsString) -> error::UResult<Vec<u8>> {
505    #[cfg(any(unix, target_os = "wasi"))]
506    let v = s.into_vec();
507    #[cfg(not(any(unix, target_os = "wasi")))]
508    let v = s
509        .into_string()
510        .map_err(|_| {
511            error::UUsageError::new(1, "invalid UTF-8 was detected in one or more arguments")
512        })?
513        .into();
514
515    Ok(v)
516}
517
518/// Equivalent to `std::BufRead::lines` which outputs each line as a `Vec<u8>`,
519/// which avoids panicking on non UTF-8 input.
520pub fn read_byte_lines<R: std::io::Read>(
521    mut buf_reader: BufReader<R>,
522) -> impl Iterator<Item = std::io::Result<Vec<u8>>> {
523    iter::from_fn(move || {
524        let mut buf = Vec::with_capacity(256);
525
526        match buf_reader.read_until(b'\n', &mut buf) {
527            Ok(0) => None,
528            Err(e) => Some(Err(e)),
529            Ok(_) => {
530                // Trim (\r)\n
531                if buf.ends_with(b"\n") {
532                    buf.pop();
533                    if buf.ends_with(b"\r") {
534                        buf.pop();
535                    }
536                }
537
538                Some(Ok(buf))
539            }
540        }
541    })
542}
543
544/// Equivalent to `std::BufRead::lines` which outputs each line as an `OsString`
545/// This won't panic on non UTF-8 characters on Unix,
546/// but it still will on Windows.
547pub fn read_os_string_lines<R: std::io::Read>(
548    buf_reader: BufReader<R>,
549) -> impl Iterator<Item = std::io::Result<OsString>> {
550    read_byte_lines(buf_reader)
551        .map(|byte_line_res| byte_line_res.map(|bl| os_string_from_vec(bl).expect("UTF-8 error")))
552}
553
554/// Prompt the user with a formatted string and returns `true` if they reply `'y'` or `'Y'`
555///
556/// This macro functions accepts the same syntax as `format!`. The prompt is written to
557/// `stderr`. A space is also printed at the end for nice spacing between the prompt and
558/// the user input. Any input starting with `'y'` or `'Y'` is interpreted as `yes`.
559///
560/// # Examples
561/// ```
562/// use uucore::prompt_yes;
563/// let file = "foo.rs";
564/// prompt_yes!("Do you want to delete '{file}'?");
565/// ```
566/// will print something like below to `stderr` (with `util_name` substituted by the actual
567/// util name) and will wait for user input.
568/// ```txt
569/// util_name: Do you want to delete 'foo.rs'?
570/// ```
571#[macro_export]
572macro_rules! prompt_yes(
573    ($($args:tt)+) => ({
574        use std::io::Write;
575        eprint!("{}: ", uucore::util_name());
576        eprint!($($args)+);
577        eprint!(" ");
578        let res = std::io::stderr().flush().map_err(|err| {
579            $crate::error::USimpleError::new(1, err.to_string())
580        });
581        uucore::show_if_err!(res);
582        uucore::read_yes()
583    })
584);
585
586/// Represent either a character or a byte.
587/// Used to iterate on partially valid UTF-8 data
588#[derive(Debug, Clone, Copy, PartialEq, Eq)]
589pub enum CharByte {
590    Char(char),
591    Byte(u8),
592}
593
594impl From<char> for CharByte {
595    fn from(value: char) -> Self {
596        Self::Char(value)
597    }
598}
599
600impl From<u8> for CharByte {
601    fn from(value: u8) -> Self {
602        Self::Byte(value)
603    }
604}
605
606impl From<&u8> for CharByte {
607    fn from(value: &u8) -> Self {
608        Self::Byte(*value)
609    }
610}
611
612struct Utf8ChunkIterator<'a> {
613    iter: Box<dyn Iterator<Item = CharByte> + 'a>,
614}
615
616impl Iterator for Utf8ChunkIterator<'_> {
617    type Item = CharByte;
618
619    fn next(&mut self) -> Option<Self::Item> {
620        self.iter.next()
621    }
622}
623
624impl<'a> From<Utf8Chunk<'a>> for Utf8ChunkIterator<'a> {
625    fn from(chk: Utf8Chunk<'a>) -> Self {
626        Self {
627            iter: Box::new(
628                chk.valid()
629                    .chars()
630                    .map(CharByte::from)
631                    .chain(chk.invalid().iter().map(CharByte::from)),
632            ),
633        }
634    }
635}
636
637/// Iterates on the valid and invalid parts of a byte sequence with regard to
638/// the UTF-8 encoding.
639pub struct CharByteIterator<'a> {
640    iter: Box<dyn Iterator<Item = CharByte> + 'a>,
641}
642
643impl<'a> CharByteIterator<'a> {
644    /// Make a `CharByteIterator` from a byte slice.
645    /// [`CharByteIterator`]
646    pub fn new(input: &'a [u8]) -> Self {
647        Self {
648            iter: Box::new(input.utf8_chunks().flat_map(Utf8ChunkIterator::from)),
649        }
650    }
651}
652
653impl Iterator for CharByteIterator<'_> {
654    type Item = CharByte;
655
656    fn next(&mut self) -> Option<Self::Item> {
657        self.iter.next()
658    }
659}
660
661pub trait IntoCharByteIterator<'a> {
662    fn iter_char_bytes(self) -> CharByteIterator<'a>;
663}
664
665impl<'a> IntoCharByteIterator<'a> for &'a [u8] {
666    fn iter_char_bytes(self) -> CharByteIterator<'a> {
667        CharByteIterator::new(self)
668    }
669}
670
671#[cfg(test)]
672mod tests {
673    use super::*;
674    use std::ffi::OsStr;
675
676    fn make_os_vec(os_str: &OsStr) -> Vec<OsString> {
677        vec![
678            OsString::from("test"),
679            OsString::from("สวัสดี"), // spell-checker:disable-line
680            os_str.to_os_string(),
681        ]
682    }
683
684    #[cfg(any(unix, target_os = "redox"))]
685    fn test_invalid_utf8_args_lossy(os_str: &OsStr) {
686        // assert our string is invalid utf8
687        assert!(os_str.to_os_string().into_string().is_err());
688        let test_vec = make_os_vec(os_str);
689        let collected_to_str = test_vec.clone().into_iter().collect_lossy();
690        // conservation of length - when accepting lossy conversion no arguments may be dropped
691        assert_eq!(collected_to_str.len(), test_vec.len());
692        // first indices identical
693        for index in 0..2 {
694            assert_eq!(collected_to_str[index], test_vec[index].to_str().unwrap());
695        }
696        // lossy conversion for string with illegal encoding is done
697        assert_eq!(
698            *collected_to_str[2],
699            os_str.to_os_string().to_string_lossy()
700        );
701    }
702
703    #[cfg(any(unix, target_os = "redox"))]
704    fn test_invalid_utf8_args_ignore(os_str: &OsStr) {
705        // assert our string is invalid utf8
706        assert!(os_str.to_os_string().into_string().is_err());
707        let test_vec = make_os_vec(os_str);
708        let collected_to_str = test_vec.clone().into_iter().collect_ignore();
709        // assert that the broken entry is filtered out
710        assert_eq!(collected_to_str.len(), test_vec.len() - 1);
711        // assert that the unbroken indices are converted as expected
712        for index in 0..2 {
713            assert_eq!(
714                collected_to_str.get(index).unwrap(),
715                test_vec.get(index).unwrap().to_str().unwrap()
716            );
717        }
718    }
719
720    #[test]
721    fn valid_utf8_encoding_args() {
722        // create a vector containing only correct encoding
723        let test_vec = make_os_vec(&OsString::from("test2"));
724        // expect complete conversion without losses, even when lossy conversion is accepted
725        let _ = test_vec.into_iter().collect_lossy();
726    }
727
728    #[cfg(any(unix, target_os = "redox"))]
729    #[test]
730    fn invalid_utf8_args_unix() {
731        use std::os::unix::ffi::OsStrExt;
732
733        let source = [0x66, 0x6f, 0x80, 0x6f];
734        let os_str = OsStr::from_bytes(&source[..]);
735        test_invalid_utf8_args_lossy(os_str);
736        test_invalid_utf8_args_ignore(os_str);
737    }
738
739    #[test]
740    fn test_format_usage() {
741        assert_eq!(format_usage("expr EXPRESSION"), "expr EXPRESSION");
742        assert_eq!(
743            format_usage("expr EXPRESSION\nexpr OPTION"),
744            "expr EXPRESSION\n       expr OPTION"
745        );
746    }
747}