formatted

This commit is contained in:
matthieu.jolimaitre 2022-09-25 10:30:30 +02:00
parent 37e5b5e555
commit 7f694cd6c2
9 changed files with 333 additions and 332 deletions

1
rustfmt.toml Normal file
View file

@ -0,0 +1 @@
"hard_tabs" = true

View file

@ -1,3 +1,3 @@
pub fn main(_files: Vec<String>) { pub fn main(_files: Vec<String>) {
todo!() todo!()
} }

View file

@ -1,6 +1,6 @@
use std::{ use std::{
env, fs, env, fs,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use ron::ser::PrettyConfig; use ron::ser::PrettyConfig;
@ -10,71 +10,71 @@ use crate::utils::Apply;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct Config { pub struct Config {
name: String, name: String,
main_file: String, main_file: String,
test_file: String, test_file: String,
includes: Vec<String>, includes: Vec<String>,
} }
impl Config { impl Config {
const CONFIG_FILE_NAME: &'static str = "pi.ron"; const CONFIG_FILE_NAME: &'static str = "pi.ron";
pub fn new(name: String) -> Self { pub fn new(name: String) -> Self {
Self { Self {
name, name,
main_file: "main.c".into(), main_file: "main.c".into(),
test_file: "test.c".into(), test_file: "test.c".into(),
includes: vec![], includes: vec![],
} }
} }
pub fn write(&self, mut path: PathBuf) { pub fn write(&self, mut path: PathBuf) {
path.extend([Self::CONFIG_FILE_NAME]); path.extend([Self::CONFIG_FILE_NAME]);
let content = let content =
ron::ser::to_string_pretty(self, PrettyConfig::default().struct_names(true)).unwrap(); ron::ser::to_string_pretty(self, PrettyConfig::default().struct_names(true)).unwrap();
fs::write(path, content).unwrap(); fs::write(path, content).unwrap();
} }
pub fn get_current() -> Self { pub fn get_current() -> Self {
let path = env::current_dir().unwrap(); let path = env::current_dir().unwrap();
Self::get(&path) Self::get(&path)
.unwrap_or_else(|| Self::new(path.file_name().unwrap().to_str().unwrap().to_string())) .unwrap_or_else(|| Self::new(path.file_name().unwrap().to_str().unwrap().to_string()))
} }
pub fn get(path: &Path) -> Option<Self> { pub fn get(path: &Path) -> Option<Self> {
let path = path.to_path_buf().canonicalize().unwrap(); let path = path.to_path_buf().canonicalize().unwrap();
Self::try_get(&path).or_else(|| path.parent().and_then(Self::get)) Self::try_get(&path).or_else(|| path.parent().and_then(Self::get))
} }
pub fn name(&self) -> &str { pub fn name(&self) -> &str {
&self.name &self.name
} }
pub fn main_file(&self) -> &str { pub fn main_file(&self) -> &str {
&self.main_file &self.main_file
} }
pub fn test_file(&self) -> &str { pub fn test_file(&self) -> &str {
&self.test_file &self.test_file
} }
pub fn includes(&self) -> &Vec<String> { pub fn includes(&self) -> &Vec<String> {
&self.includes &self.includes
} }
fn try_get(path: &Path) -> Option<Self> { fn try_get(path: &Path) -> Option<Self> {
let path = path.to_path_buf().apply(|p| p.push(Self::CONFIG_FILE_NAME)); let path = path.to_path_buf().apply(|p| p.push(Self::CONFIG_FILE_NAME));
fs::read_to_string(path) fs::read_to_string(path)
.ok() .ok()
.and_then(|content| ron::from_str(&content).ok()) .and_then(|content| ron::from_str(&content).ok())
} }
} }
pub fn create(path: String) { pub fn create(path: String) {
let absolute = fs::canonicalize(&path).unwrap(); let absolute = fs::canonicalize(&path).unwrap();
if !absolute.is_dir() { if !absolute.is_dir() {
panic!("not a directory"); panic!("not a directory");
} }
let name = absolute.file_name().unwrap(); let name = absolute.file_name().unwrap();
let config = Config::new(name.to_str().unwrap().to_string()); let config = Config::new(name.to_str().unwrap().to_string());
config.write(absolute); config.write(absolute);
} }

View file

@ -11,80 +11,80 @@ pub mod watch;
#[derive(Parser)] #[derive(Parser)]
pub struct Arguments { pub struct Arguments {
#[clap(subcommand)] #[clap(subcommand)]
command: Commands, command: Commands,
} }
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
#[derive(Subcommand)] #[derive(Subcommand)]
pub enum Commands { pub enum Commands {
/// Checks a source file for conformance with piscine limitations. /// Checks a source file for conformance with piscine limitations.
check { check {
/// File to check. /// File to check.
files: Vec<String>, files: Vec<String>,
}, },
/// Runs a set of files or the default target. /// Runs a set of files or the default target.
run { run {
/// Files to run. /// Files to run.
files: Vec<String>, files: Vec<String>,
}, },
/// Runs tests contained within a particular test file or /// Runs tests contained within a particular test file or
test { test {
/// Wether to capture standard output or not. /// Wether to capture standard output or not.
#[clap(short, long)] #[clap(short, long)]
capture: bool, capture: bool,
/// Files to run tests from. /// Files to run tests from.
files: Vec<String>, files: Vec<String>,
/// Specific tests to run. /// Specific tests to run.
#[clap(short, long)] #[clap(short, long)]
tests: Vec<String>, tests: Vec<String>,
}, },
/// Watches changes to source files and re run them /// Watches changes to source files and re run them
watch { watch {
/// Files to run. /// Files to run.
files: Vec<String>, files: Vec<String>,
}, },
/// ///
init { path: String }, init { path: String },
} }
fn append_includes(list: &mut Vec<String>) { fn append_includes(list: &mut Vec<String>) {
list.extend( list.extend(
Config::get_current() Config::get_current()
.includes() .includes()
.iter() .iter()
.map(|f| f.to_string()), .map(|f| f.to_string()),
); );
} }
fn main() { fn main() {
let args: Arguments = Parser::parse(); let args: Arguments = Parser::parse();
match args.command { match args.command {
Commands::check { files } => check::main(files), Commands::check { files } => check::main(files),
Commands::run { mut files } => { Commands::run { mut files } => {
append_includes(&mut files); append_includes(&mut files);
run::main(files); run::main(files);
} }
Commands::test { Commands::test {
capture, capture,
mut files, mut files,
tests, tests,
} => { } => {
append_includes(&mut files); append_includes(&mut files);
let tests = (!tests.is_empty()).then_some(tests); let tests = (!tests.is_empty()).then_some(tests);
test::main(capture, files, tests) test::main(capture, files, tests)
} }
Commands::watch { mut files } => { Commands::watch { mut files } => {
append_includes(&mut files); append_includes(&mut files);
watch::main(files) watch::main(files)
} }
Commands::init { path } => config::create(path), Commands::init { path } => config::create(path),
} }
} }

View file

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

View file

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

View file

@ -3,39 +3,39 @@ use std::fs;
use crate::tasks::{CompileTask, GenTask, RunTask}; use crate::tasks::{CompileTask, GenTask, RunTask};
pub fn main(_capture: bool, files: Vec<String>, _test: Option<Vec<String>>) { pub fn main(_capture: bool, files: Vec<String>, _test: Option<Vec<String>>) {
// let includes = files // let includes = files
// .iter() // .iter()
// .cloned() // .cloned()
// .map(|p| PathBuf::from_str(&p).unwrap()) // .map(|p| PathBuf::from_str(&p).unwrap())
// .collect::<Vec<_>>(); // .collect::<Vec<_>>();
for path in files { for path in files {
let content = fs::read_to_string(&path).unwrap(); let content = fs::read_to_string(&path).unwrap();
let tests = find_tests(content); let tests = find_tests(content);
for test in tests { for test in tests {
let content = gen_test_main(fs::canonicalize(&path).unwrap().to_str().unwrap(), &test); let content = gen_test_main(fs::canonicalize(&path).unwrap().to_str().unwrap(), &test);
let generated_code = GenTask::new(content).run(); let generated_code = GenTask::new(content).run();
// compile with all files // compile with all files
//let files = includes.clone().apply(|v| v.insert(0, generated_code)); //let files = includes.clone().apply(|v| v.insert(0, generated_code));
let generated_bin = CompileTask::new(vec![generated_code]).run().unwrap(); let generated_bin = CompileTask::new(vec![generated_code]).run().unwrap();
// run // run
RunTask::new(generated_bin).run().unwrap(); RunTask::new(generated_bin).run().unwrap();
} }
} }
} }
pub fn find_tests(source: String) -> Vec<String> { pub fn find_tests(source: String) -> Vec<String> {
source source
.split([' ', '(', ')', ';']) .split([' ', '(', ')', ';'])
.filter(|name| name.starts_with("test_")) .filter(|name| name.starts_with("test_"))
.map(String::from) .map(String::from)
.collect() .collect()
} }
pub fn gen_test_main(path: &str, test: &str) -> String { pub fn gen_test_main(path: &str, test: &str) -> String {
format!( format!(
" "
void ____test(); void ____test();
int main() {{ int main() {{
@ -49,5 +49,5 @@ void ____test() {{
{test}(); {test}();
}} }}
" "
) )
} }

View file

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

View file

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