Compare commits

..

12 commits

12 changed files with 174 additions and 64 deletions

6
Cargo.lock generated
View file

@ -138,7 +138,7 @@ checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]] [[package]]
name = "epitls-pi" name = "epitls-pi"
version = "1.3.1" version = "1.5.2"
dependencies = [ dependencies = [
"chrono", "chrono",
"clap", "clap",
@ -475,9 +475,9 @@ dependencies = [
[[package]] [[package]]
name = "termion" name = "termion"
version = "1.5.6" version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" checksum = "659c1f379f3408c7e5e84c7d0da6d93404e3800b6b9d063ba24436419302ec90"
dependencies = [ dependencies = [
"libc", "libc",
"numtoa", "numtoa",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "epitls-pi" name = "epitls-pi"
version = "1.3.1" version = "1.5.2"
edition = "2021" edition = "2021"
license = "GPL-3.0+" license = "GPL-3.0+"
description = "A little helper tool meant to ease the developpment of the C piscine at EPITA/Toulouse." description = "A little helper tool meant to ease the developpment of the C piscine at EPITA/Toulouse."
@ -22,7 +22,7 @@ notify = "5.0"
notify-debouncer-mini = "0.2" notify-debouncer-mini = "0.2"
ron = "0.8" ron = "0.8"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
termion = "1.5" termion = "2.0"
[profile.release] [profile.release]
lto = true lto = true

View file

@ -2,7 +2,7 @@
## Description ## Description
A little helper tool meant to ease the developpment of the C piscine at A little helper tool meant to ease the developpment of the C homeworks at
EPITA/Toulouse. EPITA/Toulouse.
## Usage ## Usage
@ -19,7 +19,7 @@ OPTIONS:
SUBCOMMANDS: SUBCOMMANDS:
check Checks a source file for conformance with piscine limitations check Checks a source file for conformance with piscine limitations
help Print this message or the help of the given subcommand(s) help Print this message or the help of the given subcommand(s)
init init Initializes a project directory configuration, useful for custom flags, includes and custop push messages
run Runs a set of files or the default target run Runs a set of files or the default target
test Runs tests contained within a particular test file or the default test file test Runs tests contained within a particular test file or the default test file
watch Watches changes to the project included files and runs a command on changes watch Watches changes to the project included files and runs a command on changes
@ -33,5 +33,8 @@ SUBCOMMANDS:
## TODO ## TODO
- [ ] add support and switch on run/test for strict mode. - [ ] add support and switch on run/test for strict mode.
- [ ] add `--` syntax to run subcommand for parameter piping. - [x] add `-p` flag to run subcommand with parameter piping.
- [ ] flag on push to add automatically
- [ ] prevent double includes. - [ ] prevent double includes.
- [ ] flag on init to copy personnal environment
- [ ] gc subcommand to free cache

View file

@ -7,9 +7,7 @@ use crate::{
utils::{log_failure, log_process, log_success}, utils::{log_failure, log_process, log_success},
}; };
/// TODO: fill with appropriate rules mod formatting;
const FORMAT_CONFIG: &str = r#"{BasedOnStyle: llvm}"#;
mod testables; mod testables;
pub fn main(files: Vec<String>) { pub fn main(files: Vec<String>) {
@ -35,7 +33,7 @@ pub enum Diff {
fn check_formatting(file: String) { fn check_formatting(file: String) {
let content = fs::read_to_string(&file).unwrap(); let content = fs::read_to_string(&file).unwrap();
let formatted = FormatTask::new(file.clone(), FORMAT_CONFIG.into()).run(); let formatted = FormatTask::new(file.clone(), formatting::formatted_config()).run();
let mut line_number = 0usize; let mut line_number = 0usize;
let mut invalid = false; let mut invalid = false;
let differences = diff::lines(&content, &formatted) let differences = diff::lines(&content, &formatted)
@ -94,7 +92,7 @@ fn check_formatting(file: String) {
pub fn format(files: Vec<String>) { pub fn format(files: Vec<String>) {
for file in files { for file in files {
let mut formatted = FormatTask::new(file.clone(), FORMAT_CONFIG.into()).run(); let mut formatted = FormatTask::new(file.clone(), formatting::formatted_config()).run();
if !formatted.ends_with('\n') { if !formatted.ends_with('\n') {
formatted += "\n"; formatted += "\n";
} }

17
src/check/formatting.rs Normal file
View file

@ -0,0 +1,17 @@
pub type StaticConf = [(&'static str, &'static str)];
/// TODO: fill with appropriate rules
pub const FORMAT_CONFIG: &StaticConf = &[
// (key, value)
("BasedOnStyle", "GNU"),
("IndentWidth", "4"),
];
pub fn formatted_config() -> String {
let middle = FORMAT_CONFIG
.iter()
.map(|(key, value)| format!("{key}: {value}"))
.collect::<Vec<_>>()
.join(", ");
format!("{{ {middle} }}")
}

View file

@ -1,18 +1,12 @@
fn ends_with_newline(source: String) -> Result<(), String> { pub type RuleResult = Result<(), String>;
if !source.ends_with('\n') {
Err("source does not end with newline".into())
} else {
Ok(())
}
}
pub struct Rule { pub struct Rule {
name: String, name: String,
test: Box<dyn Fn(String) -> Result<(), String>>, test: Box<dyn Fn(String) -> RuleResult>,
} }
impl Rule { impl Rule {
pub fn new(name: impl ToString, test: impl 'static + Fn(String) -> Result<(), String>) -> Self { pub fn new(name: impl ToString, test: impl 'static + Fn(String) -> RuleResult) -> Self {
let name = name.to_string(); let name = name.to_string();
let test = Box::new(test); let test = Box::new(test);
Self { name, test } Self { name, test }
@ -22,15 +16,32 @@ impl Rule {
&self.name &self.name
} }
pub fn test(&self, source: String) -> Result<(), String> { pub fn test(&self, source: String) -> RuleResult {
(self.test)(source) (self.test)(source)
} }
} }
fn ends_with_newline(source: String) -> RuleResult {
if !source.ends_with('\n') {
Err("source does not end with newline".into())
} else {
Ok(())
}
}
fn function_under_50l(source: String) -> RuleResult {
for character in source.chars() {
let _c: char = character;
}
Ok(())
}
/// TODO: fill with appropriate rules /// TODO: fill with appropriate rules
pub fn tests() -> Vec<Rule> { pub fn tests() -> Vec<Rule> {
vec![ vec![
// rules // rules
Rule::new("ends_with_newline", ends_with_newline), Rule::new("ends_with_newline", ends_with_newline),
Rule::new("function_under_50l", function_under_50l),
] ]
} }

View file

@ -63,6 +63,7 @@ impl Config {
Self::try_get(&path).or_else(|| path.parent().and_then(Self::get)) Self::try_get(&path).or_else(|| path.parent().and_then(Self::get))
} }
// get path of the current config file
pub fn get_path(path: &Path) -> Option<PathBuf> { pub fn get_path(path: &Path) -> Option<PathBuf> {
let path = path.to_path_buf().canonicalize().unwrap(); let path = path.to_path_buf().canonicalize().unwrap();
Self::try_get_path(&path).or_else(|| path.parent().and_then(Self::get_path)) Self::try_get_path(&path).or_else(|| path.parent().and_then(Self::get_path))
@ -72,16 +73,38 @@ impl Config {
&self.identifier &self.identifier
} }
pub fn main_file(&self) -> &str { pub fn main_file(&self) -> String {
&self.main_file Self::try_absolute(self.main_file.clone())
} }
pub fn test_file(&self) -> &str { pub fn test_file(&self) -> String {
&self.test_file Self::try_absolute(self.test_file.clone())
} }
pub fn includes(&self) -> &Vec<String> { fn try_absolute(path: String) -> String {
&self.includes if let Some(conf_path) = Self::get_local_path() {
let dir_path = conf_path.parent().unwrap();
dir_path.join(path).to_str().unwrap().to_string()
} else {
path
}
}
pub fn includes(&self) -> Vec<String> {
self.includes
.iter()
.map(|p| Self::try_absolute(p.clone()))
.flat_map(|p| {
if p.contains('*') {
glob::glob(&p)
.unwrap()
.map(|p| p.unwrap().to_str().unwrap().to_string())
.collect()
} else {
vec![p]
}
})
.collect()
} }
pub fn strict_mode(&self) -> bool { pub fn strict_mode(&self) -> bool {

View file

@ -1,5 +1,3 @@
use std::env;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use config::Config; use config::Config;
@ -35,8 +33,13 @@ pub enum Commands {
/// Runs a set of files or the default target. /// Runs a set of files or the default target.
run { run {
// disables fsanitize
#[clap(short, long)]
disable_sanitize: bool,
/// Files to run. /// Files to run.
files: Vec<String>, files: Vec<String>,
#[clap(short, long)]
pass: Vec<String>,
}, },
/// Runs tests contained within a particular test file or the default test file. /// Runs tests contained within a particular test file or the default test file.
@ -62,10 +65,10 @@ pub enum Commands {
/// Initializes a project directory configuration, useful for custom flags, includes and custop push messages. /// Initializes a project directory configuration, useful for custom flags, includes and custop push messages.
init { init {
/// Identifier for the automated tests.
prefix: String,
/// Path to the folder containing the project. /// Path to the folder containing the project.
path: Option<String>, path: String,
/// Identifier for the automated tests.
prefix: Option<String>,
/// e /// e
#[clap(short, long)] #[clap(short, long)]
tests: bool, tests: bool,
@ -84,15 +87,17 @@ fn append_includes(list: &mut Vec<String>) {
); );
} }
fn compilation_args() -> Vec<String> { fn compilation_args(sanitize: bool) -> Vec<String> {
let mut args = vec![ let mut args = vec![
"-Wall".to_string(), "-Wall".to_string(),
"-Wextra".to_string(), "-Wextra".to_string(),
"-std=c99".to_string(), "-std=c99".to_string(),
"-g".to_string(), "-g".to_string(),
"-fsanitize=address".to_string(), "-pedantic".to_string(),
// "-pedantic".to_string(),
]; ];
if sanitize {
args.push("-fsanitize=address".to_string())
}
if Config::get_local_or_default().strict_mode() { if Config::get_local_or_default().strict_mode() {
args.push("-Werror".to_string()); args.push("-Werror".to_string());
} }
@ -107,13 +112,17 @@ fn main() {
Commands::format { files } => check::format(files), Commands::format { files } => check::format(files),
Commands::run { mut files } => { Commands::run {
disable_sanitize,
mut files,
pass,
} => {
if files.is_empty() { if files.is_empty() {
files.push(Config::get_local_or_default().main_file().to_string()); files.push(Config::get_local_or_default().main_file());
} }
append_includes(&mut files); append_includes(&mut files);
let args = compilation_args(); let args = compilation_args(!disable_sanitize);
run::main(files, args); run::main(files, args, pass);
} }
Commands::test { Commands::test {
@ -122,11 +131,12 @@ fn main() {
// tests, // tests,
} => { } => {
if files.is_empty() { if files.is_empty() {
files.push(Config::get_local_or_default().test_file().to_string()); files.push(Config::get_local_or_default().test_file());
} }
append_includes(&mut files); let mut includes = vec![];
let args = compilation_args(); append_includes(&mut includes);
test::main(capture, files, args) let args = compilation_args(true);
test::main(capture, files, includes, args)
} }
Commands::watch { command, files } => { Commands::watch { command, files } => {
let mut files = files.unwrap_or_default(); let mut files = files.unwrap_or_default();
@ -139,8 +149,7 @@ fn main() {
prefix, prefix,
tests, tests,
} => { } => {
let path = let prefix = prefix.unwrap_or_else(|| ".".to_string());
path.unwrap_or_else(|| env::current_dir().unwrap().to_str().unwrap().to_string());
let prefix = prefix.trim().trim_end_matches('*'); let prefix = prefix.trim().trim_end_matches('*');
config::create(path.clone(), prefix.to_string()); config::create(path.clone(), prefix.to_string());
if tests { if tests {

View file

@ -3,7 +3,7 @@ use crate::{
utils::{log_failure, log_process}, utils::{log_failure, log_process},
}; };
pub fn main(files: Vec<String>, flags: Vec<String>) -> Option<()> { pub fn main(files: Vec<String>, flags: Vec<String>, passed: 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();
log_process("compiling"); log_process("compiling");
let mut task = CompileTask::new(source_file); let mut task = CompileTask::new(source_file);
@ -19,6 +19,7 @@ pub fn main(files: Vec<String>, flags: Vec<String>) -> Option<()> {
log_process("running"); log_process("running");
RunTask::new(compiled) RunTask::new(compiled)
.with_args(passed)
.run() .run()
.map(Option::from) .map(Option::from)
.unwrap_or_else(|_| { .unwrap_or_else(|_| {

View file

@ -1,11 +1,14 @@
use std::{ use std::{
fs, fs,
path::PathBuf, path::PathBuf,
process::{Command, ExitStatus, Stdio}, process::{exit, Command, ExitStatus, Stdio},
}; };
use termion::color;
use crate::utils::{ use crate::utils::{
log_command_run, log_separator_bottom, log_separator_top, tmp_file_path, Apply, log_command_run, log_failure, log_separator_bottom, log_separator_top, remove_dupes,
tmp_file_path, Apply,
}; };
pub struct CompileTask { pub struct CompileTask {
@ -41,9 +44,9 @@ impl CompileTask {
} }
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 sources = self.files.clone();
sources.push(proc_source); // sources.push(proc_source);
self.compile(sources) self.compile(sources)
} }
@ -55,17 +58,27 @@ impl CompileTask {
} }
pub fn compile(&self, sources: Vec<PathBuf>) -> Result<PathBuf, ExitStatus> { pub fn compile(&self, sources: Vec<PathBuf>) -> Result<PathBuf, ExitStatus> {
let mut sources = sources
.iter()
.map(|s| s.to_str().unwrap().to_string())
.collect();
remove_dupes(&mut sources);
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);
if self.verbose { if self.verbose {
log_command_run(&command); log_command_run(&command);
log_separator_top(); log_separator_top();
} }
println!(
"{}{command:?}{}",
color::Fg(color::AnsiValue(8)),
color::Fg(color::Reset)
);
let status = command.status().unwrap(); let status = command.status().unwrap();
if self.verbose { if self.verbose {
log_separator_bottom(); log_separator_bottom();
@ -77,6 +90,7 @@ impl CompileTask {
pub struct RunTask { pub struct RunTask {
file: PathBuf, file: PathBuf,
verbose: bool, verbose: bool,
args: Vec<String>,
} }
impl RunTask { impl RunTask {
@ -84,17 +98,23 @@ impl RunTask {
Self { Self {
file, file,
verbose: false, verbose: false,
args: vec![],
} }
} }
pub fn with_args(mut self, mut args: Vec<String>) -> Self {
self.args.append(&mut args);
self
}
pub fn with_verbose(mut self) -> Self { pub fn with_verbose(mut self) -> Self {
self.verbose = true; self.verbose = true;
self self
} }
pub fn run(self) -> Result<(), ExitStatus> { pub fn run(self) -> Result<(), ExitStatus> {
let mut command = Command::new("sh"); let mut command = Command::new(self.file.to_str().unwrap());
command.args(["-c", self.file.to_str().unwrap()]); command.args(self.args);
if self.verbose { if self.verbose {
log_command_run(&command); log_command_run(&command);
log_separator_top(); log_separator_top();
@ -146,10 +166,20 @@ impl FormatTask {
.arg(format!("-style={config}")) .arg(format!("-style={config}"))
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.stderr(Stdio::piped()); .stderr(Stdio::piped());
command.status().unwrap();
let result = command.output().unwrap().stdout; let status = command.status().unwrap();
String::from_utf8(result).unwrap() let out = command.output().unwrap().stdout;
let out = String::from_utf8(out).unwrap();
let err = command.output().unwrap().stderr;
let err = String::from_utf8(err).unwrap();
if !status.success() {
log_failure("failed formatting");
println!("{out}");
eprintln!("{err}");
exit(1);
}
out
} }
} }

View file

@ -1,13 +1,14 @@
use std::{fs, thread, time::Duration}; use std::{fs, path::PathBuf, thread, time::Duration};
use crate::{ use crate::{
tasks::{CompileTask, GenTask, RunTask}, tasks::{CompileTask, GenTask, RunTask},
utils::{log_failure, log_process, log_success}, utils::{log_failure, log_process, log_success},
}; };
pub fn main(_capture: bool, files: Vec<String>, args: Vec<String>) { pub fn main(_capture: bool, test_files: Vec<String>, includes: Vec<String>, args: Vec<String>) {
log_process("testing"); log_process("testing");
for path in files { let includes: Vec<_> = includes.into_iter().map(PathBuf::from).collect();
for path in test_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 {
@ -18,7 +19,10 @@ pub fn main(_capture: bool, files: Vec<String>, args: Vec<String>) {
thread::sleep(Duration::from_millis(100)); thread::sleep(Duration::from_millis(100));
// compile with all files // compile with all files
let mut task = CompileTask::new(vec![generated_code]); let mut files = vec![generated_code];
let mut local_includes = includes.clone(); // TODO : filter out current test : already included
files.append(&mut local_includes);
let mut task = CompileTask::new(files);
for flag in args.clone() { for flag in args.clone() {
task = task.with_flag(flag); task = task.with_flag(flag);
} }
@ -54,6 +58,8 @@ int main(int argc, char** argv) {{
return 0; return 0;
}} }}
#define main __pi_hidden_main
#include \"{path}\" #include \"{path}\"
void __pi_test() {{ void __pi_test() {{

View file

@ -1,4 +1,4 @@
use std::{fs, path::PathBuf, process::Command}; use std::{collections::HashSet, fs, mem::swap, path::PathBuf, process::Command};
use chrono::Utc; use chrono::Utc;
use termion::color; use termion::color;
@ -97,3 +97,15 @@ pub fn log_error(input: impl AsRef<str>) {
log_pi_prefix(); log_pi_prefix();
println!("error: {input}"); println!("error: {input}");
} }
pub fn remove_dupes(input: &mut Vec<String>) {
let mut tmp = vec![];
swap(input, &mut tmp);
let mut set = HashSet::new();
for str in tmp {
set.insert(str);
}
for str in set {
input.push(str);
}
}