Check slides



examples/other/check-slides/src/main.rs
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::iter::FromIterator;
use std::path::Path;
use std::path::PathBuf;
use std::process::exit;
use std::process::Command;
use std::sync::mpsc;
use std::thread;

// use regex::Regex;

// from all the md files extract the list of included files
// from the examples/ directory list all the files
// make sure each file is included and is only included once

// Run every rs file, if there is an out file compare the results.
//
const ROOT: &str = "../../..";

fn main() {
    let verbose = false;
    std::env::set_current_dir(ROOT).unwrap();
    let md_files = get_md_files();
    let imported_files = get_imported_files(md_files);
    let examples = get_all_the_examples();

    let mut count = 0;
    for filename in examples {
        if filename.ends_with("swp") {
            continue;
        }
        if filename.ends_with("counter.db") {
            continue;
        }
        if !imported_files.contains(&filename) {
            println!("ERROR Unused file: `{}`", filename);
            count += 1;
        }
    }

    let crates = get_crates(Path::new("examples"));

    let clippy_error = check_crates(crates, verbose);

    if clippy_error > 0 {
        eprintln!("There are {clippy_error} examples with clippy errors.");
        exit(1);
    }

    if count > 0 {
        eprintln!("There are {count} unused examples");
        exit(1);
    }
}

fn check_crates(crates: Vec<PathBuf>, verbose: bool) -> i32 {
    println!("check_crates");
    let mut clippy_error = 0;
    let number_of_crates = crates.len();

    // We want run max_threads at once, when one is finished we start a new one
    // Then we collect the messages from the remaining ones.
    let (tx, rx) = mpsc::channel();
    let max_threads = 10;
    let mut thread_count = 0;
    let mut started = 0;
    let mut finished = 0;

    for (ix, crate_folder) in crates.into_iter().enumerate() {
        started += 1;
        if verbose {
            println!("crate: {}/{}, {:?}", ix+1, number_of_crates, crate_folder);
        }
        let mytx = tx.clone();

        thread::spawn(move || {
            let res = check_crate(&crate_folder);
            mytx.send(res).unwrap();
        });
        thread_count += 1;
        if thread_count >= max_threads {
            let received = rx.recv().unwrap();
            if !received {
                clippy_error += 1;
            }
            finished += 1;
        }
    }

    for received in rx {
        //println!("received {}", thread_count);
        finished += 1;
        if !received {
            clippy_error += 1;
        }
        if finished >= started {
            break;
        }
    }

    println!("check crates done");
    clippy_error
}

fn check_crate(crate_folder: &PathBuf) -> bool {
    let folder = crate_folder.clone().into_os_string().into_string().unwrap();
    let folders = vec![
        "examples/intro/formatting-required",
        "examples/intro/print",
        "examples/functions/declare-twice",
        "examples/variables/change-literal-string",
        "examples/variables/immutable-string",
        "examples/variables/immutable-number",
        "examples/variables/cannot-change-type",
        "examples/tuples/empty",
        "examples/numbers/small-integers-unfit-in-i8",
        "examples/numbers/rounding-float",
        "examples/booleans/other",
        "examples/ownership/mutable-string-in-immutable-variable",
        "examples/files/list-tree",  // TODO
        "examples/files/open-file-handling", // TODO
        "examples/arrays/numbers-change",
        "examples/types/type-mismatch",
        "examples/errors/out-of-bounds-array",
        "examples/errors/div-by-zero-hard-coded",
        "examples/advanced-functions/calculator", // TODO
    ].into_iter().map(|x| x.to_string()).collect::<String>();
    if folders.contains(&folder) {
        return true;
    }
    //println!("current_dir:  {:?}", std::env::current_dir().unwrap());
    //println!("crate_folder: {:?}", crate_folder);
    //std::env::set_current_dir(crate_folder).unwrap();
    let result = Command::new("cargo")
        .arg("clippy")
        .arg("--")
        .arg("--deny")
        .arg("warnings")
        .current_dir(crate_folder)
        .output()
        .expect("failed to execute process");

    if !result.status.success() {
        //println!("{}", result.status);
        println!("ERROR in crate: {:?}", crate_folder);
        //println!("{}", std::str::from_utf8(&result.stdout).unwrap());
        //println!("{}", std::str::from_utf8(&result.stderr).unwrap());
        //std::process::exit(1);
    }
    result.status.success()

    //let result = Command::new("cargo")
    //    .arg("fmt")
    //    .arg("--check")
    //    .output()
    //    .expect("failed to execute process");
    //println!("{}", std::str::from_utf8(&result.stdout).unwrap());
    //println!("{}", std::str::from_utf8(&result.stderr).unwrap());
    //println!("{}", result.status);
}

fn get_crates(path: &Path) -> Vec<PathBuf> {
    println!("get_crates");
    let crates = get_crates_recoursive(path);
    println!("get_crates done\n");
    crates
}

fn get_crates_recoursive(path: &Path) -> Vec<PathBuf> {
    let mut crates: Vec<PathBuf> = vec![];
    for entry in path.read_dir().expect("read_dir call failed").flatten() {
        if entry.path().ends_with("target") {
            continue;
        }
        //println!("{:?}", entry);
        if entry.path().ends_with("Cargo.toml") {
            //println!("cargo: {:?}", entry.path().parent());
            crates.push(entry.path().parent().unwrap().to_path_buf());
        }
        if entry.path().is_dir() {
            crates.extend(get_crates_recoursive(entry.path().as_path()));
        }
    }

    crates
}

// TODO: go deeper than 2 levels to also handle examples/*/src/main.rs
// TODO: but exclude examples/*/target/
fn get_all_the_examples() -> Vec<String> {
    println!("get_all_the_examples");

    let exclude: Vec<String> = [
        "examples/image/create-image/image.png",
        "examples/other/multi_counter_with_manual_csv/counter.csv",
        "examples/other/send-mail-with-sendgrid/config.txt",
    ]
    .iter()
    .map(|path| path.to_string())
    .collect();
    let pathes = get_examples(Path::new("examples"));
    let pathes: Vec<String> = pathes
        .iter()
        .filter(|path| !exclude.contains(path))
        .cloned()
        .collect();

    println!("get_all_the_examples done\n");
    pathes
}

fn get_examples(path: &Path) -> Vec<String> {
    let mut examples: Vec<String> = vec![];
    for entry in path.read_dir().expect("read_dir call failed").flatten() {
        if entry.path().ends_with("Cargo.lock") {
            continue;
        }
        if entry.path().ends_with("Cargo.toml") {
            continue;
        }

        if entry.path().is_dir() {
            if entry.path().ends_with("target") {
                continue;
            }
            examples.extend(get_examples(entry.path().as_path()));
            continue;
        }
        //dbg!(&entry);

        if entry.path().is_file() {
            examples.push(entry.path().into_os_string().into_string().unwrap());
            continue;
        }
    }
    examples
    //return Vec::from_iter( examples.iter().map(|s| s.clone().into_os_string().into_string().expect("Bad") ) );
}

fn get_imported_files(md_files: Vec<PathBuf>) -> Vec<String> {
    println!("get_imported_files");
    // println!("{:?}", md_files);
    // ![](examples/arrays/update_hash.rs)
    // let re = Regex::new(r"^!\[\]]\((.*)\)\s*$").unwrap();
    let mut imported_files = vec![];
    for filename in md_files {
        //println!("{:?}", filename);
        match File::open(filename.as_path()) {
            Ok(file) => {
                let reader = BufReader::new(file);
                for line in reader.lines() {
                    let line = line.unwrap();
                    if line.starts_with("![](") && line.ends_with(')') {
                        //println!("{}", &line[4..line.len()-1])
                        imported_files.push((line[4..line.len() - 1]).to_string());
                    }
                }
            }
            Err(error) => {
                println!("Error opening file {:?}: {}", filename, error);
            }
        }
    }
    println!("get_imported_files done\n");
    return Vec::from_iter(imported_files.iter().map(|s| s.to_string()));
}

fn get_md_files() -> Vec<PathBuf> {
    println!("get_md_files");
    let mut md_files = vec![];
    let path = Path::new(".");
    for entry in path.read_dir().expect("read_dir call failed").flatten() {
        let filename = entry.path();
        //println!("{:?}", filename); //.as_path());
        let extension = filename.extension();
        if let Some(value) = extension {
            if value == "md" {
                // println!("{:?}", filename);
                //println!("{}", filename);
                md_files.push(filename);
            }
        }
        //println!("{:?}", extension.unwrap())
    }

    println!("get_md_files done\n");
    md_files
}