init
This commit is contained in:
commit
8e2a03e3a7
11 changed files with 1045 additions and 0 deletions
3
src/check.rs
Normal file
3
src/check.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub fn main(_file: String) {
|
||||
todo!()
|
||||
}
|
73
src/main.rs
Normal file
73
src/main.rs
Normal 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
28
src/run.rs
Normal 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
87
src/tasks.rs
Normal 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
29
src/test.rs
Normal 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
85
src/utils.rs
Normal 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
62
src/watch.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue