cli

src/lib.rs

#![allow(unused)]
fn main() {
/*!
cli illustrates the use of [Vec] and the Error Handling with [Option] and [Result].
It also showcases struct enums.
*/

pub mod io;
mod minimum;
pub mod something_or_nothing;

#[cfg(test)]
mod tests {
    use crate::minimum::Minimum;
    use crate::something_or_nothing::{SomethingOrNothing, find_min};

    #[test]
    fn test_creation() {
        let n1: SomethingOrNothing<i32> = SomethingOrNothing::default();
        assert!(n1 == SomethingOrNothing::default());
        let n2: SomethingOrNothing<i32> = SomethingOrNothing::new(1);
        assert!(n2 == SomethingOrNothing::new(1));
    }

    #[test]
    #[should_panic]
    fn test_failure_creation() {
        let n2: SomethingOrNothing<i32> = SomethingOrNothing::new(1);
        assert!(n2 == SomethingOrNothing::default());
        assert!(n2 == SomethingOrNothing::new(2));
    }

    #[test]
    fn test_min() {
        let a = vec![1, 5, -1, 2, 0, 10, 11, 0, 3];
        let min = find_min(&a);
        assert!(min == SomethingOrNothing::new(-1));
    }

    #[test]
    fn test_min_i32() {
        let x = 5;
        let y = 10;
        assert_eq!(Minimum::min(x, y), x);
        assert_eq!(Minimum::min(y, x), x);
        assert_eq!(Minimum::min(x, x), x);
        assert_eq!(Minimum::min(y, y), y);
    }

    #[test]
    fn test_min_something_or_nothing() {
        let x = SomethingOrNothing::new(5i32);
        let y = SomethingOrNothing::new(10i32);
        let z = SomethingOrNothing::default();
        assert!(x.min(y) == x);
        assert!(y.min(x) == x);
        assert!(z.min(y) == y);
        assert!(y.min(z) == y);
        assert!(z.min(z) == z);
    }
}
}

src/main.rs

use cli::io;

fn main() -> Result<(), String> {
    io::read_command_line_builder()?;
    Ok(())
}

src/io.rs

#![allow(unused)]
fn main() {
use std::{
    fs::File,
    io::{BufReader, Read, Write},
};

// ANCHOR: use_clap
use clap::{Arg, Command, Parser, value_parser};
// ANCHOR_END: use_clap

// ANCHOR: consts
const COMMAND: &str = "cli";
const AUTHOR: &str = "Orestis Malaspinas";
const VERSION: &str = "0.1.0";
// ANCHOR_END: consts

use crate::something_or_nothing::find_min;

// ANCHOR: read_from_urandom
fn read_from_urandom(count: usize) -> Result<Vec<i32>, String> {
    // ANCHOR: open
    let file = File::open("/dev/urandom").map_err(|_| "Could not open /dev/urandom")?;
    // ANCHOR_END: open
    // ANCHOR: read
    let mut buf_reader = BufReader::new(file);
    let mut numbers = vec![0; count * 4];
    buf_reader
        .read_exact(&mut numbers)
        .map_err(|_| "Could not read numbers")?;
    // ANCHOR_END: read
    // ANCHOR: convert_to_i32
    Ok(numbers
        .chunks(4)
        .map(|i| i32::from_be_bytes(i.try_into().unwrap()))
        .collect::<Vec<_>>())
    // ANCHOR_END: convert_to_i32
}
// ANCHOR_END: read_from_urandom

// ANCHOR: write_to_file
fn write_to_file(output: &str, numbers: &[i32]) -> Result<(), String> {
    // ANCHOR: create
    let mut file = File::create(output).map_err(|_| format!("Failed to create {output}"))?;
    // ANCHOR_END: create
    // ANCHOR: write
    writeln!(file, "Among the Somethings in the list:")
        .map_err(|_| "Failed to write header into file.")?;
    for n in numbers {
        write!(file, "{n} ").map_err(|_| format!("Failed to write {n} into file."))?;
    }
    writeln!(file,).map_err(|_| "Failed to write carriage return into file.")?;
    writeln!(file, "{}", find_min(numbers).to_string())
        .map_err(|_| "Failed to write minimum value into file.")?;
    // ANCHOR_END: write
    Ok(())
}
// ANCHOR_END: write_to_file

/// Reads i32 from the command line and returns a [Vec] containing
/// these numbers. Returns errors when the parsing fails.
pub fn read_command_line_builder() -> Result<(), String> {
    // ANCHOR: matches
    let matches =
    // ANCHOR: new_command
        Command::new(COMMAND)
            .author(AUTHOR)
            .version(VERSION)
    // ANCHOR_END: new_command
    // ANCHOR: new_args
            .arg(
                Arg::new("numbers") // id
                    .short('n')         // version courte -n
                    .long("numbers")    // ou longue --numbers
                    .help("A list of i32 numbers") // l'aide
                    .num_args(1..) // combien il y a d'entrées
                    .allow_negative_numbers(true) // on peut avoir des négatifs
                    .value_parser(value_parser!(i32)) // on veut s'assurer que ça soit des nombres
                    .required(false), // optionnel
            )
            .arg(
                Arg::new("count")
                    .short('c')
                    .long("count")
                    .help("How many random numbers we want?")
                    .value_parser(value_parser!(usize))
                    .conflicts_with("numbers") // impossible d'avoir -c et -n
                    .required(false),
            )
            .arg(
                Arg::new("output")
                    .short('o')
                    .long("output")
                    .help("Should we write output in a file?")
                    .required(false),
            )
    // ANCHOR: new_args
            .get_matches();
    // ANCHOR_END: matches

    // ANCHOR: numbers_matches
    let numbers = if let Some(count) =
        // ANCHOR: get_one_matches
        matches.get_one::<usize>("count")
    // ANCHOR_END: get_one_matches
    {
        read_from_urandom(*count)?
    } else if let Some(numbers) =
        // ANCHOR: get_many_matches
        matches.get_many::<i32>("numbers")
    // ANCHOR_END: get_many_matches
    {
        numbers.copied().collect()
    } else {
        Vec::new()
    };
    // ANCHOR_END: numbers_matches

    // ANCHOR: output_matches
    if let Some(output) =
        // ANCHOR: get_one_string_matches
        matches.get_one::<String>("output")
    // ANCHOR_END: get_one_string_matches
    {
        write_to_file(output, &numbers)?;
    } else {
        println!("Among the Somethings in the list:");
        print_tab(&numbers);
        println!("{}", find_min(&numbers).to_string());
    }
    // ANCHOR_END: output_matches

    Ok(())
}

/// Does not compile without the feature derive
// ANCHOR: derive
#[derive(Parser)]
// ANCHOR: command
#[command(author, version, about, long_about = None)]
// ANCHOR_END: command
struct CliMin {
    // ANCHOR: arg
    #[arg(short, long, help = "A list of i32 numbers", num_args=1.., allow_negative_numbers=true, value_parser = clap::value_parser!(i32))]
    numbers: Option<Vec<i32>>,
    // ANCHOR_END: arg
    #[arg(short, long, help = "How many random numbers we want?", value_parser = clap::value_parser!(usize), conflicts_with = "numbers")]
    count: Option<usize>,
    #[arg(short, long, help = "Filename for writing the numbers.")]
    output: Option<String>,
}
// ANCHOR_END: derive

/// Reads i32 from the command line and returns a [Vec] containing
/// these numbers. Returns errors when the parsing fails.
// ANCHOR: read_command_line_derive
pub fn read_command_line_derive() -> Result<(), String> {
    // ANCHOR: parse
    let cli = CliMin::parse();
    // ANCHOR_END: parse
    let numbers = if let Some(count) = cli.count {
        read_from_urandom(count)?
    } else if let Some(numbers) = cli.numbers {
        numbers
    } else {
        Vec::new()
    };
    if let Some(output) = cli.output {
        write_to_file(&output, &numbers)?;
    } else {
        println!("Among the Somethings in the list:");
        print_tab(&numbers);
        println!("{}", find_min(&numbers).to_string());
    }
    Ok(())
}
// ANCHOR_END: read_command_line_derive

/// Prints all the elements of the `tab`.
/// Tab is borrowed here
pub fn print_tab(tab: &Vec<i32>) {
    for t in tab {
        print!("{} ", t);
    }
    println!();
}
}

src/minimum.rs

#![allow(unused)]
fn main() {
// If we remove Copy, we have a problem with the t in tab
// in the computation of the minimum.
pub trait Minimum: Copy {
    fn min(self, rhs: Self) -> Self;
}

impl Minimum for i32 {
    fn min(self, rhs: Self) -> Self {
        if self < rhs { self } else { rhs }
    }
}
}

src/something_or_nothing.rs

use std::fmt::Display;

use crate::minimum::Minimum;

/// A generic newtype that wraps an Option<T>.
#[derive(Clone, Copy)]
pub struct SomethingOrNothing<T>(Option<T>);

impl<T: Minimum + Display> SomethingOrNothing<T> {
    pub fn new(val: T) -> Self {
        SomethingOrNothing(Some(val))
    }
    /// A method that returns the content of a SomethingOrNothing as a String.
    pub fn to_string(&self) -> String {
        match self.0 {
            None => String::from("Nothing."),
            Some(val) => format!("Something is: {}", val),
        }
    }
}

impl<T> Default for SomethingOrNothing<T> {
    /// By Default a [SomethingOrNothing] is a nothing.
    fn default() -> Self {
        SomethingOrNothing(None)
    }
}

impl<T: PartialEq + Minimum> PartialEq for SomethingOrNothing<T> {
    fn eq(&self, other: &Self) -> bool {
        match (self.0, other.0) {
            (None, None) => true,
            (Some(lhs), Some(rhs)) => lhs == rhs,
            _ => false,
        }
    }
}

impl<T: Minimum + Display> Minimum for SomethingOrNothing<T> {
    fn min(self, rhs: Self) -> Self {
        match (self.0, rhs.0) {
            (None, None) => SomethingOrNothing(None),
            (Some(lhs), Some(rhs)) => SomethingOrNothing::new(lhs.min(rhs)),
            (None, Some(rhs)) => SomethingOrNothing::new(rhs),
            (Some(lhs), None) => SomethingOrNothing::new(lhs),
        }
    }
}

/// Computes the minimum of an Array of a type T which implements the [Minimum] trait.
/// Returns a [Some] containing the minimum value
/// or [None] if no minimum value was found.
///
/// # Examples
///
/// ```
/// # use cli::something_or_nothing::{SomethingOrNothing, find_min};
/// # fn main() {
/// let tab = vec![10, 32, 12, 43, 52, 53, 83, 2, 9];
/// let min = find_min(&tab);
/// assert!(min == SomethingOrNothing::new(2));
/// # }
/// ```
///
/// ```
/// # use cli::something_or_nothing::{SomethingOrNothing, find_min};
/// # fn main() {
/// let tab: Vec<i32> = vec![];
/// let min = find_min(&tab);
/// assert!(min == SomethingOrNothing::default());
/// # }
/// ```
pub fn find_min<T: Minimum + Display>(tab: &[T]) -> SomethingOrNothing<T> {
    let mut minimum: SomethingOrNothing<T> = SomethingOrNothing(None);
    // Here, if T is Copyable, t is not moved in the loop
    for t in tab {
        minimum = minimum.min(SomethingOrNothing::new(*t));
    }
    minimum
}