This commit is contained in:
JOLIMAITRE Matthieu 2022-09-20 03:08:36 +02:00
commit 8e2a03e3a7
11 changed files with 1045 additions and 0 deletions

3
src/check.rs Normal file
View file

@ -0,0 +1,3 @@
pub fn main(_file: String) {
todo!()
}

73
src/main.rs Normal file
View file

@ -0,0 +1,73 @@
use clap::{Parser, Subcommand};
pub mod check;
pub mod run;
pub mod tasks;
pub mod test;
pub mod utils;
pub mod watch;
#[derive(Parser)]
pub struct Arguments {
#[clap(subcommand)]
command: Commands,
}
// TODO: turn files into file lists
#[allow(non_camel_case_types)]
#[derive(Subcommand)]
pub enum Commands {
/// Checks a source file for conformance with piscine limitations.
check {
/// File to check.
/// Supports globing
#[clap(default_value_t = String::from("*"))]
file: String,
},
/// Runs a file
run {
/// File to run.
#[clap(default_value_t = String::from("./main.c"))]
file: String,
},
/// Runs tests contained within a particular test file or
test {
/// W.I.P. Wether to capture standard output or not.
#[clap(short, long)]
capture: bool,
/// File to run tests from.
#[clap(default_value_t = String::from("./test.c"))]
file: String,
/// Specific test to run.
test: Option<String>,
},
/// Watches changes to source files and re run them
watch {
/// File to run.
#[clap(default_value_t = String::from("./main.c"))]
file: String,
},
}
fn main() {
let args: Arguments = Parser::parse();
match args.command {
Commands::check { file } => check::main(file),
Commands::run { file } => {
run::main(file);
}
Commands::test {
capture,
file,
test,
} => test::main(capture, file, test),
Commands::watch { file } => watch::main(file),
}
}

28
src/run.rs Normal file
View file

@ -0,0 +1,28 @@
use crate::{
tasks::{CompileTask, RunTask},
utils::{log_failure, log_success},
};
pub fn main(file: String) -> Option<()> {
let source_file = file.into();
let compiled = CompileTask::new(source_file)
.with_flag("-Wall")
.with_flag("-Wextra")
.with_flag("-std=c99")
.run()
.map(Option::from)
.unwrap_or_else(|_| {
log_failure("compilation failed");
None
})?;
log_success("compilation successful");
RunTask::new(compiled)
.run()
.map(Option::from)
.unwrap_or_else(|_| {
log_failure("process failure");
None
})?;
log_success("process exited successfully");
Some(())
}

87
src/tasks.rs Normal file
View file

@ -0,0 +1,87 @@
use std::{
fs,
path::PathBuf,
process::{Command, ExitStatus},
};
use crate::utils::{log_command_run, log_separator, tmp_file_path, Apply};
pub struct CompileTask {
file: PathBuf,
addition: Vec<String>,
flags: Vec<String>,
}
// TODO: split compile & compile raw
impl CompileTask {
pub fn new(file: PathBuf) -> Self {
Self {
file,
addition: vec![],
flags: vec![],
}
}
pub fn with_addition(mut self, code: impl ToString) -> Self {
self.addition.push(code.to_string());
self
}
pub fn with_flag(mut self, flag: impl ToString) -> Self {
self.flags.push(flag.to_string());
self
}
pub fn run(self) -> Result<PathBuf, ExitStatus> {
let proc_source = self.gen_source();
let sources = vec![proc_source, self.file.clone()];
self.compile(sources)
}
pub fn gen_source(&self) -> PathBuf {
let mut output_path = tmp_file_path();
// TODO: make use of supplement
output_path.set_extension("c");
fs::write(&output_path, "").unwrap();
output_path
}
pub fn compile(&self, sources: Vec<PathBuf>) -> Result<PathBuf, ExitStatus> {
let output_path = tmp_file_path().apply(|o| o.set_extension("b"));
let output_path_ref = output_path.to_str().unwrap();
let mut command = Command::new("gcc");
command
.args(["-o", output_path_ref])
.args(self.flags.clone())
.args(sources.iter().map(|s| s.to_str().unwrap()));
log_command_run(&command);
log_separator();
let status = command.status().unwrap();
log_separator();
status.success().then_some(output_path).ok_or(status)
}
}
pub struct RunTask {
file: PathBuf,
}
impl RunTask {
pub fn new(file: PathBuf) -> Self {
Self { file }
}
pub fn run(self) -> Result<(), ExitStatus> {
let mut command = Command::new(self.file);
log_command_run(&command);
log_separator();
let status = command.status().unwrap();
log_separator();
if status.success() {
Ok(())
} else {
Err(status)
}
}
}

29
src/test.rs Normal file
View file

@ -0,0 +1,29 @@
pub fn main(_capture: bool, _file: String, _test: Option<String>) {
let content = todo!();
let tests = find_tests(content);
for test in tests {
// compile
// run
}
}
pub fn find_tests(source: String) -> Vec<String> {
source
.split([' ', '(', ')', ';'])
.filter(|name| &name[0..5] == "test_")
.map(String::from)
.collect()
}
pub fn gen_test_main(test_file: &str, test: &str) -> String {
format!(
"
#include \"{test_file}\"
int main() {{
{test}();
return 0;
}}
"
)
}

85
src/utils.rs Normal file
View file

@ -0,0 +1,85 @@
use std::{fs, path::PathBuf, process::Command};
use chrono::Utc;
use termion::color;
pub fn tmp_file_path() -> PathBuf {
let ms = Utc::now().timestamp_millis().to_string();
let mut path: PathBuf = ["/", "tmp", "epitls-pi"].iter().collect();
fs::create_dir_all(&path).unwrap();
path.push(ms);
path
}
pub trait Apply: Sized {
fn apply<F, O>(mut self, f: F) -> Self
where
F: FnOnce(&mut Self) -> O,
{
f(&mut self);
self
}
}
impl<T> Apply for T {}
fn log_pi_prefix() {
print!(
"{}[pi] {}",
color::Fg(color::LightBlue),
color::Fg(color::Reset)
)
}
pub fn log_command_run(command: &Command) {
log_pi_prefix();
let prefix = format_process("running ");
let value = format_variable(&format!("{command:?}"));
let suffix = format_process(" ...");
println!("{prefix}{value}{suffix}");
}
pub fn log_separator() {
println!("────────────────")
}
pub fn log_failure(text: &str) {
log_pi_prefix();
let text = format!("{}{text}{}", color::Fg(color::Red), color::Fg(color::Reset));
println!("{text}");
}
pub fn log_success(text: &str) {
log_pi_prefix();
let text = format_success(text);
println!("{text}");
}
pub fn log_process(text: &str) {
log_pi_prefix();
let text = format_process(text);
println!("{text}");
}
fn format_process(input: &str) -> String {
format!(
"{}{input}{}",
color::Fg(color::Blue),
color::Fg(color::Reset)
)
}
fn format_success(input: &str) -> String {
format!(
"{}{input}{}",
color::Fg(color::Green),
color::Fg(color::Reset)
)
}
fn format_variable(input: &str) -> String {
format!(
"{}{input}{}",
color::Fg(color::White),
color::Fg(color::Reset),
)
}

62
src/watch.rs Normal file
View file

@ -0,0 +1,62 @@
use std::{path::Path, sync::mpsc, time::Duration};
use notify::{Error, Event, Watcher};
use notify_debouncer_mini::new_debouncer;
use crate::{
tasks::{CompileTask, RunTask},
utils::{log_failure, log_process, log_success},
};
pub struct Repeater {
file: String,
}
impl Repeater {
pub fn new(file: String) -> Self {
Self { file }
}
pub fn repeat(&self) -> Option<()> {
let source = CompileTask::new(self.file.clone().into())
.run()
.map(Option::from)
.unwrap_or_else(|_| {
log_failure("failed compilation");
None
})?;
log_success("compilation successful");
RunTask::new(source)
.run()
.map(Option::from)
.unwrap_or_else(|_| {
log_failure("task failure");
None
})?;
log_success("task successful");
log_process("waiting for changes before re run");
Some(())
}
}
pub fn main(file: String) {
log_process(&format!("watching file '{file}'"));
let repeater = Repeater::new(file.clone());
repeater.repeat();
let (send, rec) = mpsc::channel();
let mut debouncer = new_debouncer(Duration::from_millis(100), None, send).unwrap();
debouncer
.watcher()
.watch(Path::new(&file), notify::RecursiveMode::Recursive)
.unwrap();
for events in rec {
for _ in events.unwrap() {
repeater.repeat();
}
}
}