Warning
This post was published 31 days ago. The information described in this article may have changed.
After reading another user's post on this I tried to implement said exercise as well with some more advanced techniques, namely the newtype pattern and using regex to parse valid commands.
Cargo.toml
[package]
name = "departments"
version = "0.1.0"
edition = "2024"
[dependencies]
env_logger = "0.11.8"
log = "0.4.27"
regex = "1.11.1"
main.rs
extern crate core;
use std::io::stdin;
use log::{error, info};
use command::{Command, Listing};
use company::Company;
mod command;
mod company;
fn main() {
env_logger::init();
let mut company = Company::default();
stdin().lines().map_while(Result::ok).for_each(|line| {
if let Ok(command) = line.parse() {
match command {
Command::List(listing) => match listing {
Listing::Company => println!("{company}"),
Listing::Department(department) => {
if let Some(department) = company.department(&department) {
println!("{department}");
} else {
error!("No such department: {department}");
}
}
},
Command::Add {
employee,
department,
} => {
company.department_mut(&department).add(&employee);
info!("Added {employee} to {department}");
}
}
} else {
error!("Invalid command!");
}
})
}
command.rs
use std::cell::LazyCell;
use std::str::FromStr;
use regex::Regex;
const LIST: LazyCell<Regex> =
LazyCell::new(|| Regex::new(r"list(?:\s+(\w+))?").expect("Invalid regex. This is a bug."));
const ADD: LazyCell<Regex> = LazyCell::new(|| {
Regex::new(r"add\s+(\w+)\s+to\s+(\w+)").expect("Invalid regex. This is a bug.")
});
#[derive(Debug)]
pub enum Command {
List(Listing),
Add {
employee: String,
department: String,
},
}
impl FromStr for Command {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some(captures) = LIST.captures(s) {
return if let Some(department) = captures.iter().flatten().nth(1) {
Ok(Command::List(Listing::Department(
department.as_str().into(),
)))
} else {
Ok(Command::List(Listing::Company))
};
}
if let Some(captures) = ADD.captures(s) {
let (_, [employee, department]) = captures.extract();
return Ok(Command::Add {
employee: employee.into(),
department: department.into(),
});
}
Err(())
}
}
#[derive(Debug)]
pub enum Listing {
Company,
Department(String),
}
company.rs
use std::collections::HashMap;
use std::fmt::Display;
use std::vec::IntoIter;
use department::Department;
use department_mut::DepartmentMut;
mod department;
mod department_mut;
#[derive(Clone, Debug, Default)]
pub struct Company {
departments: HashMap<String, Vec<String>>,
}
impl Company {
#[must_use]
pub fn department(&self, department: &str) -> Option<Department> {
self.departments
.get(department)
.map(AsRef::as_ref)
.map(Into::into)
}
#[must_use]
pub fn department_mut(&mut self, department: &str) -> DepartmentMut<'_> {
self.departments.entry(department.to_string()).into()
}
fn iter(&self) -> IntoIter<(&String, &Vec<String>)> {
let mut departments: Vec<_> = self.departments.iter().collect();
departments.sort_by(|(department_name_a, _), (department_name_b, _)| {
department_name_a.cmp(department_name_b)
});
departments.into_iter()
}
}
impl Display for Company {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
for (index, (department, employees)) in self.iter().enumerate() {
write!(
fmt,
"Department {department}:\n{}",
Department::from(employees.as_slice()),
)?;
if index + 1 < self.departments.len() {
writeln!(fmt)?;
}
}
Ok(())
}
}
company/department.rs
use std::fmt::Display;
#[derive(Debug)]
pub struct Department<'a> {
employees: &'a [String],
}
impl<'a> Display for Department<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut employees: Vec<_> = self.employees.to_vec();
employees.sort();
write!(f, "{}", employees.join(", "))
}
}
impl<'a> From<&'a [String]> for Department<'a> {
fn from(employees: &'a [String]) -> Self {
Self { employees }
}
}
company/department_mut.rs
use std::collections::hash_map::Entry;
#[derive(Debug)]
pub struct DepartmentMut<'entry> {
entry: Entry<'entry, String, Vec<String>>,
}
impl<'entry> DepartmentMut<'entry> {
pub fn add(self, employee: &str) {
self.entry.or_default().push(employee.into())
}
}
impl<'entry> From<Entry<'entry, String, Vec<String>>> for DepartmentMut<'entry> {
fn from(inner: Entry<'entry, String, Vec<String>>) -> Self {
Self { entry: inner }
}
}
Any thoughts on this?
1 post - 1 participant
🏷️ rust_feed