Compare commits
12 commits
6a242e1e84
...
cf316fe012
Author | SHA1 | Date | |
---|---|---|---|
cf316fe012 | |||
851593ee55 | |||
77f0a9b9a3 | |||
96e984244b | |||
058ea6a2f0 | |||
ad18c69f28 | |||
7958763bd4 | |||
7c6b9c3b2a | |||
e188609c18 | |||
a1459e575b | |||
37cad03cf3 | |||
17636fca11 |
12 changed files with 174 additions and 64 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
@ -138,7 +138,7 @@ checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
|||
|
||||
[[package]]
|
||||
name = "epitls-pi"
|
||||
version = "1.3.1"
|
||||
version = "1.5.2"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
|
@ -475,9 +475,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "termion"
|
||||
version = "1.5.6"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
|
||||
checksum = "659c1f379f3408c7e5e84c7d0da6d93404e3800b6b9d063ba24436419302ec90"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"numtoa",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "epitls-pi"
|
||||
version = "1.3.1"
|
||||
version = "1.5.2"
|
||||
edition = "2021"
|
||||
license = "GPL-3.0+"
|
||||
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"
|
||||
ron = "0.8"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
termion = "1.5"
|
||||
termion = "2.0"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## 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.
|
||||
|
||||
## Usage
|
||||
|
@ -19,7 +19,7 @@ OPTIONS:
|
|||
SUBCOMMANDS:
|
||||
check Checks a source file for conformance with piscine limitations
|
||||
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
|
||||
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
|
||||
|
@ -33,5 +33,8 @@ SUBCOMMANDS:
|
|||
## TODO
|
||||
|
||||
- [ ] 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.
|
||||
- [ ] flag on init to copy personnal environment
|
||||
- [ ] gc subcommand to free cache
|
||||
|
|
|
@ -7,9 +7,7 @@ use crate::{
|
|||
utils::{log_failure, log_process, log_success},
|
||||
};
|
||||
|
||||
/// TODO: fill with appropriate rules
|
||||
const FORMAT_CONFIG: &str = r#"{BasedOnStyle: llvm}"#;
|
||||
|
||||
mod formatting;
|
||||
mod testables;
|
||||
|
||||
pub fn main(files: Vec<String>) {
|
||||
|
@ -35,7 +33,7 @@ pub enum Diff {
|
|||
|
||||
fn check_formatting(file: String) {
|
||||
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 invalid = false;
|
||||
let differences = diff::lines(&content, &formatted)
|
||||
|
@ -94,7 +92,7 @@ fn check_formatting(file: String) {
|
|||
|
||||
pub fn format(files: Vec<String>) {
|
||||
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') {
|
||||
formatted += "\n";
|
||||
}
|
||||
|
|
17
src/check/formatting.rs
Normal file
17
src/check/formatting.rs
Normal 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} }}")
|
||||
}
|
|
@ -1,18 +1,12 @@
|
|||
fn ends_with_newline(source: String) -> Result<(), String> {
|
||||
if !source.ends_with('\n') {
|
||||
Err("source does not end with newline".into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
pub type RuleResult = Result<(), String>;
|
||||
|
||||
pub struct Rule {
|
||||
name: String,
|
||||
test: Box<dyn Fn(String) -> Result<(), String>>,
|
||||
test: Box<dyn Fn(String) -> RuleResult>,
|
||||
}
|
||||
|
||||
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 test = Box::new(test);
|
||||
Self { name, test }
|
||||
|
@ -22,15 +16,32 @@ impl Rule {
|
|||
&self.name
|
||||
}
|
||||
|
||||
pub fn test(&self, source: String) -> Result<(), String> {
|
||||
pub fn test(&self, source: String) -> RuleResult {
|
||||
(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
|
||||
pub fn tests() -> Vec<Rule> {
|
||||
vec![
|
||||
// rules
|
||||
Rule::new("ends_with_newline", ends_with_newline),
|
||||
Rule::new("function_under_50l", function_under_50l),
|
||||
]
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ impl Config {
|
|||
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> {
|
||||
let path = path.to_path_buf().canonicalize().unwrap();
|
||||
Self::try_get_path(&path).or_else(|| path.parent().and_then(Self::get_path))
|
||||
|
@ -72,16 +73,38 @@ impl Config {
|
|||
&self.identifier
|
||||
}
|
||||
|
||||
pub fn main_file(&self) -> &str {
|
||||
&self.main_file
|
||||
pub fn main_file(&self) -> String {
|
||||
Self::try_absolute(self.main_file.clone())
|
||||
}
|
||||
|
||||
pub fn test_file(&self) -> &str {
|
||||
&self.test_file
|
||||
pub fn test_file(&self) -> String {
|
||||
Self::try_absolute(self.test_file.clone())
|
||||
}
|
||||
|
||||
pub fn includes(&self) -> &Vec<String> {
|
||||
&self.includes
|
||||
fn try_absolute(path: String) -> String {
|
||||
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 {
|
||||
|
|
45
src/main.rs
45
src/main.rs
|
@ -1,5 +1,3 @@
|
|||
use std::env;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use config::Config;
|
||||
|
||||
|
@ -35,8 +33,13 @@ pub enum Commands {
|
|||
|
||||
/// Runs a set of files or the default target.
|
||||
run {
|
||||
// disables fsanitize
|
||||
#[clap(short, long)]
|
||||
disable_sanitize: bool,
|
||||
/// Files to run.
|
||||
files: Vec<String>,
|
||||
#[clap(short, long)]
|
||||
pass: Vec<String>,
|
||||
},
|
||||
|
||||
/// 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.
|
||||
init {
|
||||
/// Identifier for the automated tests.
|
||||
prefix: String,
|
||||
/// Path to the folder containing the project.
|
||||
path: Option<String>,
|
||||
path: String,
|
||||
/// Identifier for the automated tests.
|
||||
prefix: Option<String>,
|
||||
/// e
|
||||
#[clap(short, long)]
|
||||
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![
|
||||
"-Wall".to_string(),
|
||||
"-Wextra".to_string(),
|
||||
"-std=c99".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() {
|
||||
args.push("-Werror".to_string());
|
||||
}
|
||||
|
@ -107,13 +112,17 @@ fn main() {
|
|||
|
||||
Commands::format { files } => check::format(files),
|
||||
|
||||
Commands::run { mut files } => {
|
||||
Commands::run {
|
||||
disable_sanitize,
|
||||
mut files,
|
||||
pass,
|
||||
} => {
|
||||
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);
|
||||
let args = compilation_args();
|
||||
run::main(files, args);
|
||||
let args = compilation_args(!disable_sanitize);
|
||||
run::main(files, args, pass);
|
||||
}
|
||||
|
||||
Commands::test {
|
||||
|
@ -122,11 +131,12 @@ fn main() {
|
|||
// tests,
|
||||
} => {
|
||||
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 args = compilation_args();
|
||||
test::main(capture, files, args)
|
||||
let mut includes = vec![];
|
||||
append_includes(&mut includes);
|
||||
let args = compilation_args(true);
|
||||
test::main(capture, files, includes, args)
|
||||
}
|
||||
Commands::watch { command, files } => {
|
||||
let mut files = files.unwrap_or_default();
|
||||
|
@ -139,8 +149,7 @@ fn main() {
|
|||
prefix,
|
||||
tests,
|
||||
} => {
|
||||
let path =
|
||||
path.unwrap_or_else(|| env::current_dir().unwrap().to_str().unwrap().to_string());
|
||||
let prefix = prefix.unwrap_or_else(|| ".".to_string());
|
||||
let prefix = prefix.trim().trim_end_matches('*');
|
||||
config::create(path.clone(), prefix.to_string());
|
||||
if tests {
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
|||
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();
|
||||
log_process("compiling");
|
||||
let mut task = CompileTask::new(source_file);
|
||||
|
@ -19,6 +19,7 @@ pub fn main(files: Vec<String>, flags: Vec<String>) -> Option<()> {
|
|||
|
||||
log_process("running");
|
||||
RunTask::new(compiled)
|
||||
.with_args(passed)
|
||||
.run()
|
||||
.map(Option::from)
|
||||
.unwrap_or_else(|_| {
|
||||
|
|
52
src/tasks.rs
52
src/tasks.rs
|
@ -1,11 +1,14 @@
|
|||
use std::{
|
||||
fs,
|
||||
path::PathBuf,
|
||||
process::{Command, ExitStatus, Stdio},
|
||||
process::{exit, Command, ExitStatus, Stdio},
|
||||
};
|
||||
|
||||
use termion::color;
|
||||
|
||||
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 {
|
||||
|
@ -41,9 +44,9 @@ impl CompileTask {
|
|||
}
|
||||
|
||||
pub fn run(self) -> Result<PathBuf, ExitStatus> {
|
||||
let proc_source = self.gen_source();
|
||||
let mut sources = self.files.clone();
|
||||
sources.push(proc_source);
|
||||
// let proc_source = self.gen_source();
|
||||
let sources = self.files.clone();
|
||||
// sources.push(proc_source);
|
||||
self.compile(sources)
|
||||
}
|
||||
|
||||
|
@ -55,17 +58,27 @@ impl CompileTask {
|
|||
}
|
||||
|
||||
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_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()));
|
||||
.args(sources);
|
||||
if self.verbose {
|
||||
log_command_run(&command);
|
||||
log_separator_top();
|
||||
}
|
||||
println!(
|
||||
"{}{command:?}{}",
|
||||
color::Fg(color::AnsiValue(8)),
|
||||
color::Fg(color::Reset)
|
||||
);
|
||||
let status = command.status().unwrap();
|
||||
if self.verbose {
|
||||
log_separator_bottom();
|
||||
|
@ -77,6 +90,7 @@ impl CompileTask {
|
|||
pub struct RunTask {
|
||||
file: PathBuf,
|
||||
verbose: bool,
|
||||
args: Vec<String>,
|
||||
}
|
||||
|
||||
impl RunTask {
|
||||
|
@ -84,17 +98,23 @@ impl RunTask {
|
|||
Self {
|
||||
file,
|
||||
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 {
|
||||
self.verbose = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn run(self) -> Result<(), ExitStatus> {
|
||||
let mut command = Command::new("sh");
|
||||
command.args(["-c", self.file.to_str().unwrap()]);
|
||||
let mut command = Command::new(self.file.to_str().unwrap());
|
||||
command.args(self.args);
|
||||
if self.verbose {
|
||||
log_command_run(&command);
|
||||
log_separator_top();
|
||||
|
@ -146,10 +166,20 @@ impl FormatTask {
|
|||
.arg(format!("-style={config}"))
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
command.status().unwrap();
|
||||
|
||||
let result = command.output().unwrap().stdout;
|
||||
String::from_utf8(result).unwrap()
|
||||
let status = command.status().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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
14
src/test.rs
14
src/test.rs
|
@ -1,13 +1,14 @@
|
|||
use std::{fs, thread, time::Duration};
|
||||
use std::{fs, path::PathBuf, thread, time::Duration};
|
||||
|
||||
use crate::{
|
||||
tasks::{CompileTask, GenTask, RunTask},
|
||||
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");
|
||||
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 tests = find_tests(content);
|
||||
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));
|
||||
|
||||
// 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() {
|
||||
task = task.with_flag(flag);
|
||||
}
|
||||
|
@ -54,6 +58,8 @@ int main(int argc, char** argv) {{
|
|||
return 0;
|
||||
}}
|
||||
|
||||
#define main __pi_hidden_main
|
||||
|
||||
#include \"{path}\"
|
||||
|
||||
void __pi_test() {{
|
||||
|
|
14
src/utils.rs
14
src/utils.rs
|
@ -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 termion::color;
|
||||
|
@ -97,3 +97,15 @@ pub fn log_error(input: impl AsRef<str>) {
|
|||
log_pi_prefix();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue