use std::error::Error;
use std::io::Write;
use std::{env, fs, path::PathBuf, process, str};
pub type Result<T> = std::result::Result<T, Box<dyn Error>>;
static OUTPUT_DIR: &str = "output";
static INPUT_TAG: &str = "input";
static COL: usize = 80;
fn main() -> Result<()> {
let (file_name, flags) = process_args();
let current_dir = env::current_dir().expect("Can't get current directory");
let inputs = get_input_files(¤t_dir)?;
let (build_arg, binary) = get_cmd(¤t_dir, &file_name, flags[0])?;
let build_status = process::Command::new("cargo").args(build_arg).status()?;
if build_status.success() {
if flags[1] {
create_empty_folder(¤t_dir)?;
}
run_test_cases(inputs, &binary, &flags)?;
}
Ok(())
}
fn process_args() -> (String, Vec<bool>) {
let mut file_name = String::new();
let mut flags = vec![false; 3];
for arg in env::args().into_iter().skip(1) {
let mut iter = arg.chars().peekable();
if iter.next() == Some('-') {
if iter.peek() == Some(&'-') {
match arg.as_str() {
"--release" => flags[0] = true,
"--output-file" => flags[1] = true,
"--parallel" => flags[2] = true,
"--help" => {
print_help();
std::process::exit(0);
}
_ => panic!("Invalid arguments {}. Try to use -h flag", arg),
}
} else {
for c in iter {
match c {
'r' => flags[0] = true,
'o' => flags[1] = true,
'p' => flags[2] = true,
'h' => {
print_help();
std::process::exit(0);
}
_ => panic!("Invalid arguments {}. Try to use -h flag", arg),
}
}
}
} else {
match file_name.as_str() {
"" => {
file_name = arg
.split('/')
.last()
.unwrap()
.split('.')
.next()
.unwrap()
.to_string()
}
_ => panic!("Invalid arguments {}. Try to use -h flag", arg),
}
}
}
(file_name, flags)
}
fn print_help() {
println!("Usage: rust-bin [file_name] [option]");
println!("Options:");
println!(" -r, --release \t\tBuild in release mode");
println!(" -o, --output-file\t\tCreate output folder");
println!(" -p, --parallel \t\tRun test cases in parallel using all available cores");
println!(" -h, --help \t\tPrint this help message");
}
fn get_input_files(dir: &PathBuf) -> Result<Vec<PathBuf>> {
let mut inputs = Vec::new();
for entry in fs::read_dir(dir)? {
let entry = entry?.path();
if entry.is_dir() {
let folder = entry;
if folder.to_str().unwrap().to_lowercase().contains(INPUT_TAG) {
for file in fs::read_dir(&folder)? {
let file = file?.path();
if file.is_file() {
inputs.push(file);
}
}
}
} else if entry.is_file() {
let file = entry;
if file
.to_str()
.unwrap()
.split('/')
.last()
.unwrap()
.contains(INPUT_TAG)
{
inputs.push(file);
}
}
}
inputs.sort_unstable();
Ok(inputs)
}
fn get_cmd<'a>(
current_dir: &PathBuf,
file_name: &'a str,
release: bool,
) -> Result<(Vec<&'a str>, String)> {
let cmd_run = format!(
"{}/target/{}/{}",
current_dir.to_str().unwrap(),
if release { "release" } else { "debug" },
file_name
);
let mut cmd_args = vec!["build", "--bin", file_name];
if release {
cmd_args.push("--release");
}
Ok((cmd_args, cmd_run))
}
fn create_empty_folder(current_dir: &PathBuf) -> Result<()> {
for entry in current_dir.read_dir()? {
let entry = entry.unwrap().path();
if entry.to_str().unwrap().split('/').last().unwrap() == OUTPUT_DIR {
if entry.is_dir() {
fs::remove_dir_all(entry)?;
} else if entry.is_file() {
fs::remove_file(entry)?;
}
break;
}
}
fs::create_dir(OUTPUT_DIR)?;
Ok(())
}
fn run_test_cases(inputs: Vec<PathBuf>, binary: &str, flag: &[bool]) -> Result<()> {
if flag[2] {
let pool = ThreadPool::default();
if flag[1] {
for (i, input_file) in inputs.into_iter().enumerate() {
let binary = binary.to_string();
pool.execute(move || {
run(&binary, &input_file, Some(i)).unwrap();
});
}
} else {
for input_file in inputs.into_iter() {
let binary = binary.to_string();
pool.execute(move || {
run(&binary, &input_file, None).unwrap();
});
}
}
} else {
if flag[1] {
for (i, input_file) in inputs.into_iter().enumerate() {
run(&binary, &input_file, Some(i)).unwrap();
}
} else {
for input_file in inputs.into_iter() {
run(&binary, &input_file, None).unwrap();
}
}
}
Ok(())
}
fn run(binary: &str, input_file: &PathBuf, file_number: Option<usize>) -> Result<()> {
let mut process = process::Command::new(binary)
.stdin(process::Stdio::piped())
.stdout(process::Stdio::piped())
.stderr(process::Stdio::piped())
.spawn()?;
if let Some(mut stdin) = process.stdin.take() {
stdin.write_all(&fs::read(&input_file)?)?;
}
let start = std::time::Instant::now();
let output = process.wait_with_output()?;
let end = std::time::Instant::now();
let mut stdout = std::io::stdout().lock();
if let Some(i) = file_number {
print_cool(
&format!(
"{}: {}ms | output.{}.txt",
input_file.to_str().unwrap().split('/').last().unwrap(),
(end - start).as_millis(),
i
),
&mut stdout,
)?;
fs::write(format!("{}/output.{}.txt", OUTPUT_DIR, i), unsafe {
String::from_utf8_unchecked(output.stdout)
})?;
} else if !output.stdout.is_empty() {
print_cool(
&format!(
"{}: {}ms",
input_file.to_str().unwrap().split('/').last().unwrap(),
(end - start).as_millis()
),
&mut stdout,
)?;
stdout.write_fmt(format_args!("{}", unsafe {
String::from_utf8_unchecked(output.stdout)
}))?;
}
if !output.stderr.is_empty() {
print_cool("(stderr)", &mut stdout)?;
stdout.write_fmt(format_args!("{}", unsafe {
String::from_utf8_unchecked(output.stderr)
}))?;
}
Ok(())
}
fn print_cool<W: Write>(mid: &str, stdout: &mut W) -> Result<()> {
let occupied = 4 + mid.len();
let n1 = if occupied >= COL {
0
} else {
(COL - occupied) / 2
};
let occupied = 4 + mid.len() + n1;
let n2 = if occupied >= COL { 0 } else { COL - occupied };
stdout.write_fmt(format_args!(
"{}> {} <{}\n",
"-".repeat(n1),
mid,
"-".repeat(n2),
))?;
Ok(())
}
use std::{
sync::{mpsc, Arc, Mutex},
thread,
};
struct ThreadPool {
workers: Vec<Worker>,
sender: Option<mpsc::Sender<Job>>,
}
type Job = Box<dyn FnOnce() + Send + 'static>;
impl ThreadPool {
fn default() -> ThreadPool {
let size = thread::available_parallelism().unwrap().get();
let (sender, receiver) = mpsc::channel();
let receiver = Arc::new(Mutex::new(receiver));
let mut workers = Vec::with_capacity(size);
for id in 0..size {
workers.push(Worker::new(id, Arc::clone(&receiver)));
}
ThreadPool {
workers,
sender: Some(sender),
}
}
fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
let job = Box::new(f);
self.sender.as_ref().unwrap().send(job).unwrap();
}
}
impl Drop for ThreadPool {
fn drop(&mut self) {
drop(self.sender.take());
for worker in &mut self.workers {
if let Some(thread) = worker.thread.take() {
thread.join().unwrap();
}
}
}
}
struct Worker {
_id: usize,
thread: Option<thread::JoinHandle<()>>,
}
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
let thread = thread::spawn(move || loop {
let message = receiver.lock().unwrap().recv();
match message {
Ok(job) => job(),
Err(_) => break,
}
});
Worker {
_id: id,
thread: Some(thread),
}
}
}