Skip to content
Snippets Groups Projects
Commit 10f086b3 authored by orestis.malaspin's avatar orestis.malaspin
Browse files

Merge branch '25-cli-i-o' into 'main'

Resolve "CLI, I/O"

Closes #25

See merge request !41
parents 007df026 6eed50e3
Branches
No related tags found
1 merge request!41Resolve "CLI, I/O"
Pipeline #26007 passed
......@@ -14,4 +14,5 @@
- [Part 09](./part09.md)
- [Part 10](./part10.md)
- [Part 11](./part11.md)
- [CLI](./cli.md)
- [Part 12](./part12.md)
This diff is collapsed.
[package]
name = "cli"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "4.4.0", features = ["derive"] }
use std::{
fs::File,
io::{BufReader, Read, Write},
};
// ANCHOR: use_clap
use clap::{value_parser, Arg, Command, 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!();
}
/*!
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::{find_min, SomethingOrNothing};
#[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);
}
}
use cli::io;
fn main() -> Result<(), String> {
io::read_command_line_builder()?;
Ok(())
}
// 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
}
}
}
use std::fmt::Display;
use crate::minimum::Minimum;
/// An generic enumerated type that encapsulates and 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 static function that prints the content of a SomethingOrNothing.
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 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 is T is Copyable. Which means that t is not moved in the loop
for t in tab {
minimum = minimum.min(SomethingOrNothing::new(*t));
}
minimum
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment