.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
/target
|
||||
/test
|
||||
55
src/cli.rs
55
src/cli.rs
@@ -1,5 +1,7 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::CommandFactory;
|
||||
|
||||
#[derive(Debug, clap::Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
pub struct Args {
|
||||
@@ -10,19 +12,60 @@ pub struct Args {
|
||||
#[derive(Debug, clap::Subcommand)]
|
||||
pub enum Command {
|
||||
Generate {
|
||||
#[clap(short, long)]
|
||||
output: PathBuf,
|
||||
#[clap(short, long, value_parser = clap::value_parser!(u32).range(1..))]
|
||||
#[clap(short, long, default_value = "10", value_parser = clap::value_parser!(u32).range(1..))]
|
||||
lines: u32,
|
||||
#[clap(long = "min-op", value_parser = clap::value_parser!(u32).range(1..))]
|
||||
#[clap(long = "min-op", default_value = "2", value_parser = clap::value_parser!(u32).range(1..))]
|
||||
min_operands: u32,
|
||||
#[clap(long = "max-op", value_parser = clap::value_parser!(u32).range(1..))]
|
||||
#[clap(long = "max-op", default_value = "7", value_parser = clap::value_parser!(u32).range(1..))]
|
||||
max_operands: u32,
|
||||
},
|
||||
Translate {
|
||||
#[clap(short, long)]
|
||||
input: PathBuf,
|
||||
#[clap(short, long)]
|
||||
output: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn validate_args(args: Args) -> Args {
|
||||
match _validate_args(args) {
|
||||
Ok(args) => args,
|
||||
Err(err) => {
|
||||
let mut command = Args::command();
|
||||
err.format(&mut command).exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn _validate_args(args: Args) -> Result<Args, clap::Error> {
|
||||
match &args.command {
|
||||
Command::Generate {
|
||||
min_operands,
|
||||
max_operands,
|
||||
..
|
||||
} => {
|
||||
if min_operands > max_operands {
|
||||
return Err(clap::Error::raw(
|
||||
clap::error::ErrorKind::ValueValidation,
|
||||
"min operands must not be greater than max operands",
|
||||
));
|
||||
}
|
||||
}
|
||||
Command::Translate { input, output } => {
|
||||
if !input.is_file() {
|
||||
return Err(clap::Error::raw(
|
||||
clap::error::ErrorKind::ValueValidation,
|
||||
"input must be an existing file",
|
||||
));
|
||||
}
|
||||
|
||||
if input == output {
|
||||
return Err(clap::Error::raw(
|
||||
clap::error::ErrorKind::ValueValidation,
|
||||
"input and output must not be the same",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
44
src/generator.rs
Normal file
44
src/generator.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
pub struct FileGenerator<'a, W: std::io::Write, R: rand::Rng> {
|
||||
writer: &'a mut W,
|
||||
rand: R,
|
||||
}
|
||||
|
||||
impl<'a, W: std::io::Write, R: rand::Rng> FileGenerator<'a, W, R> {
|
||||
const OPERANDS: &'static str = "+-*/";
|
||||
const DIGITS: &'static str = "123456789";
|
||||
|
||||
pub fn new(writer: &'a mut W, rand: R) -> Self {
|
||||
Self { writer, rand }
|
||||
}
|
||||
|
||||
pub fn generate(
|
||||
&mut self,
|
||||
lines: u32,
|
||||
min_operands: u32,
|
||||
max_operands: u32,
|
||||
) -> anyhow::Result<()> {
|
||||
for _ in 0..lines {
|
||||
let operands = self.rand.gen_range(min_operands..max_operands + 1);
|
||||
for _ in 0..operands {
|
||||
let digit_idx = self.rand.gen_range(0..Self::DIGITS.len());
|
||||
let operand_idx = self.rand.gen_range(0..Self::OPERANDS.len());
|
||||
|
||||
let digit = Self::DIGITS.get(digit_idx..digit_idx + 1).unwrap();
|
||||
let operand = Self::OPERANDS.get(operand_idx..operand_idx + 1).unwrap();
|
||||
|
||||
self.writer.write_all(digit.as_bytes())?;
|
||||
self.writer.write_all(b" ")?;
|
||||
self.writer.write_all(operand.as_bytes())?;
|
||||
self.writer.write_all(b" ")?;
|
||||
}
|
||||
|
||||
let digit = self.rand.gen_range(0..Self::DIGITS.len());
|
||||
let digit = Self::DIGITS.get(digit..digit + 1).unwrap();
|
||||
self.writer.write_all(digit.as_bytes())?;
|
||||
|
||||
self.writer.write_all(b"\n")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
123
src/main.rs
123
src/main.rs
@@ -1,54 +1,13 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::{Read, Write},
|
||||
sync::OnceLock,
|
||||
};
|
||||
use std::{fs, io};
|
||||
|
||||
use clap::{CommandFactory, Parser};
|
||||
use rand::Rng;
|
||||
use clap::Parser;
|
||||
|
||||
mod cli;
|
||||
mod generator;
|
||||
mod translator;
|
||||
|
||||
static OPERANDS: &str = "+-*/";
|
||||
static DIGITS: &str = "123456789";
|
||||
|
||||
static TRANSLATION_TABLE: OnceLock<HashMap<&'static str, &'static str>> = OnceLock::new();
|
||||
|
||||
fn valudate_args(args: cli::Args) -> Result<cli::Args, clap::Error> {
|
||||
match &args.command {
|
||||
cli::Command::Generate {
|
||||
min_operands,
|
||||
max_operands,
|
||||
..
|
||||
} => {
|
||||
if min_operands > max_operands {
|
||||
return Err(clap::Error::raw(
|
||||
clap::error::ErrorKind::ValueValidation,
|
||||
"min operands must not be greater than max operands",
|
||||
));
|
||||
}
|
||||
}
|
||||
cli::Command::Translate { input, output } => {
|
||||
if input == output {
|
||||
return Err(clap::Error::raw(
|
||||
clap::error::ErrorKind::ValueValidation,
|
||||
"input and output must not be the same",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = match valudate_args(cli::Args::parse()) {
|
||||
Ok(args) => args,
|
||||
Err(err) => {
|
||||
let mut command = cli::Args::command();
|
||||
err.format(&mut command).exit();
|
||||
}
|
||||
};
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let args = cli::validate_args(cli::Args::parse());
|
||||
|
||||
match args.command {
|
||||
cli::Command::Generate {
|
||||
@@ -57,68 +16,24 @@ fn main() {
|
||||
min_operands,
|
||||
max_operands,
|
||||
} => {
|
||||
let mut rand = rand::thread_rng();
|
||||
let mut output = std::fs::File::create(output).unwrap();
|
||||
for _ in 0..lines {
|
||||
let operands = rand.gen_range(min_operands..max_operands + 1);
|
||||
for _ in 0..operands {
|
||||
let digit = rand.gen_range(0..DIGITS.len());
|
||||
let operand = rand.gen_range(0..OPERANDS.len());
|
||||
|
||||
let digit = DIGITS.get(digit..digit + 1).unwrap();
|
||||
let operand = OPERANDS.get(operand..operand + 1).unwrap();
|
||||
|
||||
output.write(digit.as_bytes()).unwrap();
|
||||
output.write(b" ").unwrap();
|
||||
output.write(operand.as_bytes()).unwrap();
|
||||
output.write(b" ").unwrap();
|
||||
if !fs::exists(output.parent().unwrap())? {
|
||||
fs::create_dir_all(output.parent().unwrap())?;
|
||||
}
|
||||
let mut output = io::BufWriter::new(fs::File::create(output)?);
|
||||
|
||||
let digit = rand.gen_range(0..DIGITS.len());
|
||||
let digit = DIGITS.get(digit..digit + 1).unwrap();
|
||||
output.write(digit.as_bytes()).unwrap();
|
||||
let rand = rand::thread_rng();
|
||||
|
||||
output.write(b"\n").unwrap();
|
||||
}
|
||||
let mut generator = generator::FileGenerator::new(&mut output, rand);
|
||||
generator.generate(lines, min_operands, max_operands)?;
|
||||
}
|
||||
cli::Command::Translate { input, output } => {
|
||||
let table = TRANSLATION_TABLE.get_or_init(|| {
|
||||
HashMap::from([
|
||||
("+", "add"),
|
||||
("-", "subtract"),
|
||||
("*", "multiply by"),
|
||||
("/", "divide by"),
|
||||
("0", "zero"),
|
||||
("1", "one"),
|
||||
("2", "two"),
|
||||
("3", "three"),
|
||||
("4", "four"),
|
||||
("5", "five"),
|
||||
("6", "six"),
|
||||
("7", "seven"),
|
||||
("8", "eight"),
|
||||
("9", "nine"),
|
||||
])
|
||||
});
|
||||
let mut input = io::BufReader::new(fs::File::open(input)?);
|
||||
let mut output = io::BufWriter::new(fs::File::create(output)?);
|
||||
|
||||
let mut input = std::fs::File::open(input).unwrap();
|
||||
let mut output = std::fs::File::create(output).unwrap();
|
||||
let mut translator = translator::FileTranslator::new(&mut input, &mut output);
|
||||
translator.translate()?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut buf = [0u8; 1];
|
||||
|
||||
loop {
|
||||
match input.read(&mut buf) {
|
||||
Ok(0) => break,
|
||||
Ok(_) => {
|
||||
let digit = std::str::from_utf8(&buf).unwrap();
|
||||
match table.get(digit) {
|
||||
Some(translation) => output.write(translation.as_bytes()).unwrap(),
|
||||
None => output.write(digit.as_bytes()).unwrap(),
|
||||
};
|
||||
}
|
||||
Err(err) => panic!("{}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
50
src/translator.rs
Normal file
50
src/translator.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use std::{collections::HashMap, sync::LazyLock};
|
||||
|
||||
pub struct FileTranslator<'a, R: std::io::Read, W: std::io::Write> {
|
||||
reader: &'a mut R,
|
||||
writer: &'a mut W,
|
||||
}
|
||||
|
||||
static TRANSLATION_TABLE: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
|
||||
HashMap::from([
|
||||
("+", "add"),
|
||||
("-", "subtract"),
|
||||
("*", "multiply by"),
|
||||
("/", "divide by"),
|
||||
("0", "zero"),
|
||||
("1", "one"),
|
||||
("2", "two"),
|
||||
("3", "three"),
|
||||
("4", "four"),
|
||||
("5", "five"),
|
||||
("6", "six"),
|
||||
("7", "seven"),
|
||||
("8", "eight"),
|
||||
("9", "nine"),
|
||||
])
|
||||
});
|
||||
|
||||
impl<'a, R: std::io::Read, W: std::io::Write> FileTranslator<'a, R, W> {
|
||||
pub fn new(reader: &'a mut R, writer: &'a mut W) -> Self {
|
||||
Self { reader, writer }
|
||||
}
|
||||
|
||||
pub fn translate(&mut self) -> anyhow::Result<()> {
|
||||
let mut buf = [0u8; 1];
|
||||
|
||||
loop {
|
||||
match self.reader.read(&mut buf)? {
|
||||
0 => break,
|
||||
_ => {
|
||||
let digit = std::str::from_utf8(&buf)?;
|
||||
match TRANSLATION_TABLE.get(digit) {
|
||||
Some(translation) => self.writer.write_all(translation.as_bytes())?,
|
||||
None => self.writer.write_all(digit.as_bytes())?,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user