From 328a963d7c05c0c98a8f8fedecb7584a777d5ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Raz=20Guzm=C3=A1n=20Macedo?= Date: Mon, 10 Jun 2024 16:04:31 -0600 Subject: [PATCH 01/15] setup xtask infra for cheatsheets --- .cargo/config.toml | 2 + Cargo.lock | 228 +++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 5 + xtask/Cargo.toml | 8 ++ xtask/src/main.rs | 37 ++++++++ xtask/src/tasks.rs | 12 +++ 6 files changed, 292 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 xtask/Cargo.toml create mode 100644 xtask/src/main.rs create mode 100644 xtask/src/tasks.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..4b01400 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[alias] +xtask = "run --manifest-path xtask/Cargo.toml --" diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..cd3d347 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,228 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "backtrace" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "cc" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "color-eyre" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "miniz_oxide" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +dependencies = [ + "adler", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "xtask" +version = "0.1.0" +dependencies = [ + "color-eyre", + "eyre", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..915fa8d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members = [ + "xtask", +] +resolver = "2" diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 0000000..8c33bbb --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "xtask" +version = "0.1.0" +edition = "2021" + +[dependencies] +color-eyre = "0.6.3" +eyre = "0.6.12" diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 0000000..b21b847 --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,37 @@ +#![deny(warnings)] + +mod tasks; + +use std::env; + +fn main() -> color_eyre::Result<()> { + color_eyre::install()?; + + // first arg is the name of the executable; skip it + let args = env::args().skip(1).collect::>(); + let args = args.iter().map(|arg| &arg[..]).collect::>(); + + match &args[..] { + ["make-cheatsheet", lang] => tasks::make_cheatsheet(lang), + ["test-cheatsheet", lang] => tasks::test_cheatsheet(lang), + _ => { + eprintln!( + "cargo xtask +Setup a programming language cheatsheet with the `training-slides` files structure. +Test all entries are in sync with `SUMMARY.md`. + +USAGE: + cargo xtask [COMMAND] + +COMMANDS: + make-cheatsheet [LANG] make LANG cheatsheet by scraping slides names in `SUMMARY.md` + test-cheatsheet [LANG] test LANG's cheatsheet (all `SUMMARY.md` items are in sheet and viceversa) + +", + ); + + Ok(()) + } + } +} + diff --git a/xtask/src/tasks.rs b/xtask/src/tasks.rs new file mode 100644 index 0000000..c0dff52 --- /dev/null +++ b/xtask/src/tasks.rs @@ -0,0 +1,12 @@ +// dummy +pub fn make_cheatsheet(lang: &str) -> color_eyre::Result<()> { + println!("make_cheatsheet for {lang}"); + Ok(()) + +} + +pub fn test_cheatsheet(lang: &str) -> color_eyre::Result<()> { + println!("make_cheatsheet for {lang}"); + Ok(()) + +} From 1357aead9623119aadd80a99f33226733fb3f544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Raz=20Guzm=C3=A1n=20Macedo?= Date: Mon, 10 Jun 2024 16:18:36 -0600 Subject: [PATCH 02/15] setup xtask infra for cheatsheets --- xtask/src/main.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/xtask/src/main.rs b/xtask/src/main.rs index b21b847..b773939 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -17,8 +17,6 @@ fn main() -> color_eyre::Result<()> { _ => { eprintln!( "cargo xtask -Setup a programming language cheatsheet with the `training-slides` files structure. -Test all entries are in sync with `SUMMARY.md`. USAGE: cargo xtask [COMMAND] From ead0e8f2a7d94f39c4bf2dcbd16d3f384eb0064f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Raz=20Guzm=C3=A1n=20Macedo?= Date: Mon, 10 Jun 2024 23:06:56 -0600 Subject: [PATCH 03/15] almost working extract_slides --- xtask/src/main.rs | 3 +- xtask/src/tasks.rs | 118 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 116 insertions(+), 5 deletions(-) diff --git a/xtask/src/main.rs b/xtask/src/main.rs index b773939..f535dca 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,4 +1,4 @@ -#![deny(warnings)] +//#![deny(warnings)] mod tasks; @@ -32,4 +32,3 @@ COMMANDS: } } } - diff --git a/xtask/src/tasks.rs b/xtask/src/tasks.rs index c0dff52..f340e17 100644 --- a/xtask/src/tasks.rs +++ b/xtask/src/tasks.rs @@ -1,12 +1,124 @@ -// dummy +use std::fs::read_to_string; + +#[derive(Debug, Eq, PartialEq)] +struct SlidesSection { + header: String, + slide_titles: Vec, +} + +fn focus_regions() -> Vec { + let stream = read_to_string("./slides/book/src/SUMMARY.md").expect("SUMMARY.md not found"); + let mut regions: Vec = vec![]; + + // Process `# Rust Fundamentals`, `# Advanced Rust` and stop at `# Advanced Rust`, in that order + let headers = vec!["# Rust Fundamentals", "# Applied Rust", "# Advanced Rust"]; + + // Yes it's a linear seach but it's relocation oblivious and we're iterating over ~100 lines at a time. Boo hoo. + for header in headers.into_iter() { + let region = stream + .lines() + // Find the slide section we are intereted int + .skip_while(|l| *l != header) + // Tricky: accumulate into a string until we find the sentinel string "\n\n#", which is a new slide section + .take_while(|l| *l != "\n\n# ") + .collect::(); + regions.push(region); + } + + assert!(regions.len() == 3); + + regions +} + +fn extract_slides(chunk: &str) -> SlidesSection { + let header = chunk + .strip_suffix('\n') + .unwrap() + .strip_prefix("# ") + .unwrap() + .into(); + + let slide_titles = chunk + .lines() + .into_iter() + .filter(|l| is_valid_slide_line(*l)) + .map(get_slide_name) + .collect::>(); + + SlidesSection { + header, + slide_titles, + } +} + +#[test] +fn test_extract_slides() { + let test = r"# Applied Rust + +Using Rust on Windows/macOS/Linux. Requires [Rust Fundamentals](#rust-fundamentals). + +* [Rust I/O Traits](./io.md) +* [Generics](./generics.md) +"; + let header = String::from("Applied Rust"); + let slide_titles = vec![String::from("Rust I/O Traits"), String::from("Generics")]; + let res = SlidesSection { + header, + slide_titles, + }; + assert_eq!(extract_slides(test), res); +} + +fn get_slide_name(line: &str) -> String { + // SAFETY + // This file should be a well formed mdbook entries + line.strip_suffix("](./") + .unwrap() + .strip_prefix("* [") + .unwrap() + .into() +} + +#[test] +fn test_get_slide_name() { + let test = "* [Methods and Traits](./methods-traits.md)"; + let res = "Methods and Traits"; + assert_eq!(res, get_slide_name(test)); + + let test2 = "* [Shared Mutability (Cell, RefCell)](./shared-mutability.md)"; + let res2 = "Shared Mutability (Cell, RefCell)"; + assert_eq!(res2, get_slide_name(test2)); +} + +fn is_valid_slide_line(line: &str) -> bool { + if line.starts_with('#') || line.is_empty() || !line.starts_with('*') || !line.ends_with(".md)") + { + false + } else { + true + } +} + +#[test] +fn test_valid_slide_lines() { + let test1 = "# Applied Rust"; + let test2 = ""; + let test3 = + "Using Rust on Windows/macOS/Linux. Requires [Rust Fundamentals](#rust-fundamentals)."; + let test4 = "* [Methods and Traits](./methods-traits.md)"; + + assert!(!is_valid_slide_line(test1)); + assert!(!is_valid_slide_line(test2)); + assert!(!is_valid_slide_line(test3)); + assert!(is_valid_slide_line(test4)); +} + pub fn make_cheatsheet(lang: &str) -> color_eyre::Result<()> { println!("make_cheatsheet for {lang}"); Ok(()) - } pub fn test_cheatsheet(lang: &str) -> color_eyre::Result<()> { println!("make_cheatsheet for {lang}"); Ok(()) - } From 5c04043da7282a95356e60ac00f5b7df5944cc05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Raz=20Guzm=C3=A1n=20Macedo?= Date: Tue, 11 Jun 2024 07:12:35 -0600 Subject: [PATCH 04/15] working get_slide_name --- xtask/src/tasks.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/xtask/src/tasks.rs b/xtask/src/tasks.rs index f340e17..f4bf15c 100644 --- a/xtask/src/tasks.rs +++ b/xtask/src/tasks.rs @@ -72,11 +72,9 @@ Using Rust on Windows/macOS/Linux. Requires [Rust Fundamentals](#rust-fundamenta fn get_slide_name(line: &str) -> String { // SAFETY // This file should be a well formed mdbook entries - line.strip_suffix("](./") - .unwrap() - .strip_prefix("* [") - .unwrap() - .into() + let top = line.rfind(']').unwrap(); + let bot = line.find('[').unwrap(); + String::from(&line[bot + 1..top]) } #[test] From 1a4b33e31cb78d4fe2b010491b8604449ad0e7f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Raz=20Guzm=C3=A1n=20Macedo?= Date: Tue, 11 Jun 2024 10:54:38 -0600 Subject: [PATCH 05/15] working utility functions for make_cheatseet --- xtask/src/tasks.rs | 214 +++++++++++++++++++++++++++++---------------- 1 file changed, 139 insertions(+), 75 deletions(-) diff --git a/xtask/src/tasks.rs b/xtask/src/tasks.rs index f4bf15c..cf78a20 100644 --- a/xtask/src/tasks.rs +++ b/xtask/src/tasks.rs @@ -6,74 +6,41 @@ struct SlidesSection { slide_titles: Vec, } -fn focus_regions() -> Vec { - let stream = read_to_string("./slides/book/src/SUMMARY.md").expect("SUMMARY.md not found"); - let mut regions: Vec = vec![]; - - // Process `# Rust Fundamentals`, `# Advanced Rust` and stop at `# Advanced Rust`, in that order - let headers = vec!["# Rust Fundamentals", "# Applied Rust", "# Advanced Rust"]; - - // Yes it's a linear seach but it's relocation oblivious and we're iterating over ~100 lines at a time. Boo hoo. - for header in headers.into_iter() { - let region = stream - .lines() - // Find the slide section we are intereted int - .skip_while(|l| *l != header) - // Tricky: accumulate into a string until we find the sentinel string "\n\n#", which is a new slide section - .take_while(|l| *l != "\n\n# ") - .collect::(); - regions.push(region); - } - - assert!(regions.len() == 3); - - regions -} - -fn extract_slides(chunk: &str) -> SlidesSection { - let header = chunk - .strip_suffix('\n') - .unwrap() - .strip_prefix("# ") - .unwrap() - .into(); - - let slide_titles = chunk - .lines() - .into_iter() - .filter(|l| is_valid_slide_line(*l)) - .map(get_slide_name) - .collect::>(); - - SlidesSection { - header, - slide_titles, +fn is_valid_slide_line(line: &str) -> bool { + if line.starts_with('#') || line.is_empty() || !line.starts_with('*') || !line.ends_with(".md)") + { + false + } else { + true } } #[test] -fn test_extract_slides() { - let test = r"# Applied Rust - -Using Rust on Windows/macOS/Linux. Requires [Rust Fundamentals](#rust-fundamentals). +fn test_valid_slide_lines() { + let test1 = "# Applied Rust"; + let test2 = ""; + let test3 = + "Using Rust on Windows/macOS/Linux. Requires [Rust Fundamentals](#rust-fundamentals)."; + let test4 = "* [Methods and Traits](./methods-traits.md)"; -* [Rust I/O Traits](./io.md) -* [Generics](./generics.md) -"; - let header = String::from("Applied Rust"); - let slide_titles = vec![String::from("Rust I/O Traits"), String::from("Generics")]; - let res = SlidesSection { - header, - slide_titles, - }; - assert_eq!(extract_slides(test), res); + assert!(!is_valid_slide_line(test1)); + assert!(!is_valid_slide_line(test2)); + assert!(!is_valid_slide_line(test3)); + assert!(is_valid_slide_line(test4)); } +const INITIAL_HEADER: &str = "# Rust Fundamentals"; +const LAST_HEADER: &str = "# No-Std Rust"; + fn get_slide_name(line: &str) -> String { // SAFETY - // This file should be a well formed mdbook entries - let top = line.rfind(']').unwrap(); - let bot = line.find('[').unwrap(); + // This line should be a well formed mdbook entries: `* [TEXT](./foo.md)` + let top = line + .rfind(']') + .expect("the markdown file entry did not have a ']'"); + let bot = line + .find('[') + .expect("the markdown file entry did not have a '['"); String::from(&line[bot + 1..top]) } @@ -88,27 +55,124 @@ fn test_get_slide_name() { assert_eq!(res2, get_slide_name(test2)); } -fn is_valid_slide_line(line: &str) -> bool { - if line.starts_with('#') || line.is_empty() || !line.starts_with('*') || !line.ends_with(".md)") - { - false - } else { - true +fn focus_regions(text: &str) -> Vec> { + //let stream = read_to_string("./slides/book/src/SUMMARY.md").expect("SUMMARY.md not found"); + let mut result: Vec> = Vec::new(); + let mut current_section: Vec = Vec::new(); + + if !text.contains(INITIAL_HEADER) { + panic!("Your INITIAL_HEADER is not part of the input. Check your `SUMMARY.md` for {INITIAL_HEADER}"); + } + if !text.contains(LAST_HEADER) { + panic!("YOUR LAST_HEADER is not part of the text input. CHECK your `SUMMARY.md` for {LAST_HEADER}"); } + + let first_header = text.find(INITIAL_HEADER).unwrap(); + let last_header = text.rfind(LAST_HEADER).unwrap(); + + let text = &text[first_header..last_header]; + + for line in text.lines() { + let trimmed_line = line.trim(); + if trimmed_line.is_empty() + || (!trimmed_line.starts_with('*') && !trimmed_line.starts_with('#')) + { + continue; + } + + if trimmed_line.starts_with("# ") { + if !current_section.is_empty() { + result.push(current_section); + current_section = Vec::new(); + } + } + current_section.push(trimmed_line.to_string()); + } + + if !current_section.is_empty() { + result.push(current_section); + } + + result } #[test] -fn test_valid_slide_lines() { - let test1 = "# Applied Rust"; - let test2 = ""; - let test3 = - "Using Rust on Windows/macOS/Linux. Requires [Rust Fundamentals](#rust-fundamentals)."; - let test4 = "* [Methods and Traits](./methods-traits.md)"; +fn test_focus_regions() { + let test = "# Summary - assert!(!is_valid_slide_line(test1)); - assert!(!is_valid_slide_line(test2)); - assert!(!is_valid_slide_line(test3)); - assert!(is_valid_slide_line(test4)); +[Start Here](./start_here.md) + +# Rust Fundamentals + +* [Overview](./overview.md) + +# Applied Rust + +Using Rust on Windows/macOS/Linux. Requires [Rust Fundamentals](#rust-fundamentals). + +* [Methods and Traits](./methods-traits.md) + +# Advanced Rust + +Topics that go beyond [Applied Rust](#applied-rust). + +* [Advanced Strings](./advanced-strings.md) + +# No-Std Rust + +Rust for the Linux Kernel and other no-std environments with an pre-existing C API. Requires [Applied Rust](#applied-rust). +"; + let res = vec![ + vec![ + "# Rust Fundamentals".to_owned(), + "* [Overview](./overview.md)".to_owned(), + ], + vec![ + "# Applied Rust".to_owned(), + "* [Methods and Traits](./methods-traits.md)".to_owned(), + ], + vec![ + "# Advanced Rust".to_owned(), + "* [Advanced Strings](./advanced-strings.md)".to_owned(), + ], + ]; + assert_eq!(focus_regions(test), res); +} + +fn extract_slides(chunk: Vec) -> SlidesSection { + dbg!(chunk.clone()); + assert!(chunk.len() > 2); + // # Rust Fundamentals + // ^ 3rd character in title + let header = String::from(&chunk[0][2..]); + + let slide_titles = chunk[1..] + .into_iter() + .map(|l| get_slide_name(l)) + .collect::>(); + + SlidesSection { + header, + slide_titles, + } +} + +#[test] +fn test_extract_slides() { + let test = "# Rust Fundamentals +* [Rust I/O Traits](./io.md) +* [Generics](./generics.md) +# No-Std Rust"; + let header = String::from("Rust Fundamentals"); + let slide_titles = vec![String::from("Rust I/O Traits"), String::from("Generics")]; + let res = SlidesSection { + header, + slide_titles, + }; + let region = focus_regions(test); + //dbg!(region.clone()); + assert_eq!(extract_slides(region[0].clone()), res); + assert!(true); } pub fn make_cheatsheet(lang: &str) -> color_eyre::Result<()> { From 3d9c2dee1d8351b2fd8d29028f845d1e7635d785 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Raz=20Guzm=C3=A1n=20Macedo?= Date: Tue, 11 Jun 2024 19:28:32 -0600 Subject: [PATCH 06/15] working make_cheatsheet writing into file --- xtask/src/tasks.rs | 111 +++++++++++++++++++++++++++++++-------------- 1 file changed, 76 insertions(+), 35 deletions(-) diff --git a/xtask/src/tasks.rs b/xtask/src/tasks.rs index cf78a20..4fc9133 100644 --- a/xtask/src/tasks.rs +++ b/xtask/src/tasks.rs @@ -1,4 +1,8 @@ -use std::fs::read_to_string; +use std::{ + fs::{read_to_string, File}, + io::Write, + path::Path, +}; #[derive(Debug, Eq, PartialEq)] struct SlidesSection { @@ -6,28 +10,23 @@ struct SlidesSection { slide_titles: Vec, } -fn is_valid_slide_line(line: &str) -> bool { - if line.starts_with('#') || line.is_empty() || !line.starts_with('*') || !line.ends_with(".md)") - { - false - } else { - true - } -} +// fn is_valid_slide_line(line: &str) -> bool { +// !line.starts_with('#') && !line.is_empty() && line.starts_with('*') && line.ends_with(".md)") +// } -#[test] -fn test_valid_slide_lines() { - let test1 = "# Applied Rust"; - let test2 = ""; - let test3 = - "Using Rust on Windows/macOS/Linux. Requires [Rust Fundamentals](#rust-fundamentals)."; - let test4 = "* [Methods and Traits](./methods-traits.md)"; - - assert!(!is_valid_slide_line(test1)); - assert!(!is_valid_slide_line(test2)); - assert!(!is_valid_slide_line(test3)); - assert!(is_valid_slide_line(test4)); -} +// #[test] +// fn test_valid_slide_lines() { +// let test1 = "# Applied Rust"; +// let test2 = ""; +// let test3 = +// "Using Rust on Windows/macOS/Linux. Requires [Rust Fundamentals](#rust-fundamentals)."; +// let test4 = "* [Methods and Traits](./methods-traits.md)"; + +// assert!(!is_valid_slide_line(test1)); +// assert!(!is_valid_slide_line(test2)); +// assert!(!is_valid_slide_line(test3)); +// assert!(is_valid_slide_line(test4)); +// } const INITIAL_HEADER: &str = "# Rust Fundamentals"; const LAST_HEADER: &str = "# No-Std Rust"; @@ -56,7 +55,6 @@ fn test_get_slide_name() { } fn focus_regions(text: &str) -> Vec> { - //let stream = read_to_string("./slides/book/src/SUMMARY.md").expect("SUMMARY.md not found"); let mut result: Vec> = Vec::new(); let mut current_section: Vec = Vec::new(); @@ -80,11 +78,9 @@ fn focus_regions(text: &str) -> Vec> { continue; } - if trimmed_line.starts_with("# ") { - if !current_section.is_empty() { - result.push(current_section); - current_section = Vec::new(); - } + if trimmed_line.starts_with("# ") && !current_section.is_empty() { + result.push(current_section); + current_section = Vec::new(); } current_section.push(trimmed_line.to_string()); } @@ -140,14 +136,13 @@ Rust for the Linux Kernel and other no-std environments with an pre-existing C A } fn extract_slides(chunk: Vec) -> SlidesSection { - dbg!(chunk.clone()); assert!(chunk.len() > 2); // # Rust Fundamentals - // ^ 3rd character in title + // ^ 3rd character in title let header = String::from(&chunk[0][2..]); let slide_titles = chunk[1..] - .into_iter() + .iter() .map(|l| get_slide_name(l)) .collect::>(); @@ -170,17 +165,63 @@ fn test_extract_slides() { slide_titles, }; let region = focus_regions(test); - //dbg!(region.clone()); assert_eq!(extract_slides(region[0].clone()), res); assert!(true); } -pub fn make_cheatsheet(lang: &str) -> color_eyre::Result<()> { - println!("make_cheatsheet for {lang}"); +pub fn make_cheatsheet(lang: &str) -> Result<(), eyre::Report>{ + // Collect headers + let text = read_to_string("./training-slides/src/SUMMARY.md").expect("SUMMARY.md not found"); + let slide_texts = focus_regions(&text); + let slide_sections: Vec = slide_texts + .iter() + .map(|l| extract_slides(l.clone())) + .collect(); + + // Check to see if a file exists + let file_str = format!("./training-slides/src/{lang}-cheatsheet.md"); + let new_file = Path::new(&file_str); + + // If so, just check if headers any headers are missing + // otherwise, create the new file, then write new file into summary.md + eprintln!("file is {file_str}"); + match File::create_new(new_file) { + Ok(mut f) => { + eprintln!("File {file_str} just created"); + let result_text = write_cheatsheet(slide_sections); + let _ = f.write_all(&result_text.as_bytes()); + //.error!("Could not write slide headers to file"); + eprintln!("Cheatsheat for {lang} written at {file_str}"); + } + Err(_) => { + eprintln!("File {lang}-cheatsheet.md already exists - checking it's in sync"); + } + } Ok(()) } -pub fn test_cheatsheet(lang: &str) -> color_eyre::Result<()> { +pub fn test_cheatsheet(lang: &str) -> Result<(), eyre::Report> { println!("make_cheatsheet for {lang}"); + let text = read_to_string("./training-slides/src/SUMMARY.md").expect("SUMMARY.md not found"); + let slide_texts = focus_regions(&text); + let slide_sections: Vec = slide_texts + .iter() + .map(|l| extract_slides(l.clone())) + .collect(); + println!("test-cheatsheet {lang} {slide_sections:?}"); Ok(()) } + +fn write_cheatsheet(slide_sections: Vec) -> String { + let mut res = String::new(); + for slide in slide_sections.iter() { + let mut section_str_buf = format!("# {}\n", slide.header); + for entry in slide.slide_titles.iter() { + let slide_title = format!("## {entry}\n"); + section_str_buf.push_str(&slide_title); + } + section_str_buf.push_str("\n"); + res.push_str(§ion_str_buf); + } + res +} From 0bcc28883484a34f354db81a5766114dcdcfd2b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Raz=20Guzm=C3=A1n=20Macedo?= Date: Tue, 11 Jun 2024 21:36:41 -0600 Subject: [PATCH 07/15] deny warnings in xtask --- xtask/src/main.rs | 2 +- xtask/src/tasks.rs | 22 +--------------------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/xtask/src/main.rs b/xtask/src/main.rs index f535dca..21b98e2 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,4 +1,4 @@ -//#![deny(warnings)] +#![deny(warnings)] mod tasks; diff --git a/xtask/src/tasks.rs b/xtask/src/tasks.rs index 4fc9133..e59dc05 100644 --- a/xtask/src/tasks.rs +++ b/xtask/src/tasks.rs @@ -10,24 +10,6 @@ struct SlidesSection { slide_titles: Vec, } -// fn is_valid_slide_line(line: &str) -> bool { -// !line.starts_with('#') && !line.is_empty() && line.starts_with('*') && line.ends_with(".md)") -// } - -// #[test] -// fn test_valid_slide_lines() { -// let test1 = "# Applied Rust"; -// let test2 = ""; -// let test3 = -// "Using Rust on Windows/macOS/Linux. Requires [Rust Fundamentals](#rust-fundamentals)."; -// let test4 = "* [Methods and Traits](./methods-traits.md)"; - -// assert!(!is_valid_slide_line(test1)); -// assert!(!is_valid_slide_line(test2)); -// assert!(!is_valid_slide_line(test3)); -// assert!(is_valid_slide_line(test4)); -// } - const INITIAL_HEADER: &str = "# Rust Fundamentals"; const LAST_HEADER: &str = "# No-Std Rust"; @@ -184,17 +166,15 @@ pub fn make_cheatsheet(lang: &str) -> Result<(), eyre::Report>{ // If so, just check if headers any headers are missing // otherwise, create the new file, then write new file into summary.md - eprintln!("file is {file_str}"); match File::create_new(new_file) { Ok(mut f) => { - eprintln!("File {file_str} just created"); let result_text = write_cheatsheet(slide_sections); let _ = f.write_all(&result_text.as_bytes()); - //.error!("Could not write slide headers to file"); eprintln!("Cheatsheat for {lang} written at {file_str}"); } Err(_) => { eprintln!("File {lang}-cheatsheet.md already exists - checking it's in sync"); + todo!(); } } Ok(()) From fbe476685667433eff75a50100319d7ce453e41b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Raz=20Guzm=C3=A1n=20Macedo?= Date: Wed, 12 Jun 2024 18:29:52 -0600 Subject: [PATCH 08/15] error when not giving an appropriate language --- xtask/src/main.rs | 11 ++++++++++- xtask/src/tasks.rs | 38 ++++++++++++++++++++++++++------------ 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 21b98e2..77a3c33 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -4,6 +4,7 @@ mod tasks; use std::env; +// Code adapted from the xtask workflow in rust-exercises fn main() -> color_eyre::Result<()> { color_eyre::install()?; @@ -11,6 +12,12 @@ fn main() -> color_eyre::Result<()> { let args = env::args().skip(1).collect::>(); let args = args.iter().map(|arg| &arg[..]).collect::>(); + let langs = vec!["python", "go", "cpp", "swift", "java", "julia", "c"]; + + if !langs.contains(&args[1]) { + panic!("{} is not a valid language name. \nExpected one of: python, go, cpp, swift, java, julia, c", &args[1]); + } + match &args[..] { ["make-cheatsheet", lang] => tasks::make_cheatsheet(lang), ["test-cheatsheet", lang] => tasks::test_cheatsheet(lang), @@ -24,7 +31,9 @@ USAGE: COMMANDS: make-cheatsheet [LANG] make LANG cheatsheet by scraping slides names in `SUMMARY.md` test-cheatsheet [LANG] test LANG's cheatsheet (all `SUMMARY.md` items are in sheet and viceversa) - + +LANG: + Valid values are `python, go, cpp, swift, java, julia, c` ", ); diff --git a/xtask/src/tasks.rs b/xtask/src/tasks.rs index e59dc05..4c30bed 100644 --- a/xtask/src/tasks.rs +++ b/xtask/src/tasks.rs @@ -10,10 +10,9 @@ struct SlidesSection { slide_titles: Vec, } -const INITIAL_HEADER: &str = "# Rust Fundamentals"; -const LAST_HEADER: &str = "# No-Std Rust"; - fn get_slide_name(line: &str) -> String { + assert!(line.starts_with("* [")); + assert!(line.ends_with(".md)")); // SAFETY // This line should be a well formed mdbook entries: `* [TEXT](./foo.md)` let top = line @@ -36,6 +35,9 @@ fn test_get_slide_name() { assert_eq!(res2, get_slide_name(test2)); } +const INITIAL_HEADER: &str = "# Rust Fundamentals"; +const LAST_HEADER: &str = "# No-Std Rust"; + fn focus_regions(text: &str) -> Vec> { let mut result: Vec> = Vec::new(); let mut current_section: Vec = Vec::new(); @@ -151,8 +153,8 @@ fn test_extract_slides() { assert!(true); } -pub fn make_cheatsheet(lang: &str) -> Result<(), eyre::Report>{ - // Collect headers +pub fn make_cheatsheet(lang: &str) -> Result<(), eyre::Report> { + // Collect slide sections, chunked by header let text = read_to_string("./training-slides/src/SUMMARY.md").expect("SUMMARY.md not found"); let slide_texts = focus_regions(&text); let slide_sections: Vec = slide_texts @@ -165,15 +167,15 @@ pub fn make_cheatsheet(lang: &str) -> Result<(), eyre::Report>{ let new_file = Path::new(&file_str); // If so, just check if headers any headers are missing - // otherwise, create the new file, then write new file into summary.md + // Otherwise, create the new file, then write new file into `SUMMARY.md` match File::create_new(new_file) { Ok(mut f) => { let result_text = write_cheatsheet(slide_sections); - let _ = f.write_all(&result_text.as_bytes()); - eprintln!("Cheatsheat for {lang} written at {file_str}"); + let _ = f.write_all(result_text.as_bytes()); + println!("Cheatsheat for {lang} written at {file_str}"); } Err(_) => { - eprintln!("File {lang}-cheatsheet.md already exists - checking it's in sync"); + println!("File {lang}-cheatsheet.md already exists - checking it's in sync"); todo!(); } } @@ -181,15 +183,27 @@ pub fn make_cheatsheet(lang: &str) -> Result<(), eyre::Report>{ } pub fn test_cheatsheet(lang: &str) -> Result<(), eyre::Report> { - println!("make_cheatsheet for {lang}"); let text = read_to_string("./training-slides/src/SUMMARY.md").expect("SUMMARY.md not found"); let slide_texts = focus_regions(&text); let slide_sections: Vec = slide_texts .iter() .map(|l| extract_slides(l.clone())) .collect(); + println!("test-cheatsheet {lang} {slide_sections:?}"); - Ok(()) + // TODO + todo!(); + // 1. Get headers as # Header + // 2. Bunch slide_titles as ## Sections + // 3. Put them into SlideSections + // let file_name = format!("./training-slices/src/{lang}-cheatsheet.md"); + // let cheatsheet_text = read_to_string(file_name).expect("SUMMARY.md not found"); + // let cheatsheet_slide_texts = focus_regions(&cheatsheet_text); + // let cheatsheet_slide_sections: Vec = cheatsheet_slide_texts + // .iter() + // .map(|l| extract_slides(l.clone())) + // .collect(); + // Ok(()) } fn write_cheatsheet(slide_sections: Vec) -> String { @@ -200,7 +214,7 @@ fn write_cheatsheet(slide_sections: Vec) -> String { let slide_title = format!("## {entry}\n"); section_str_buf.push_str(&slide_title); } - section_str_buf.push_str("\n"); + section_str_buf.push('\n'); res.push_str(§ion_str_buf); } res From 33e80143ceedfb39e9df87e0417edb0047285e9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Raz=20Guzm=C3=A1n=20Macedo?= Date: Wed, 12 Jun 2024 19:39:15 -0600 Subject: [PATCH 09/15] working test-cheatsheet --- xtask/src/main.rs | 2 +- xtask/src/tasks.rs | 58 +++++++++++++++++++++++++++++++++------------- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 77a3c33..cb4e8ee 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,4 +1,4 @@ -#![deny(warnings)] +//#![deny(warnings)] mod tasks; diff --git a/xtask/src/tasks.rs b/xtask/src/tasks.rs index 4c30bed..6436124 100644 --- a/xtask/src/tasks.rs +++ b/xtask/src/tasks.rs @@ -176,34 +176,60 @@ pub fn make_cheatsheet(lang: &str) -> Result<(), eyre::Report> { } Err(_) => { println!("File {lang}-cheatsheet.md already exists - checking it's in sync"); - todo!(); + let _ = test_cheatsheet(lang); } } Ok(()) } pub fn test_cheatsheet(lang: &str) -> Result<(), eyre::Report> { - let text = read_to_string("./training-slides/src/SUMMARY.md").expect("SUMMARY.md not found"); + let text = read_to_string("./training-slides/src/SUMMARY.md").expect("could not read_to_string - SUMMARY.md not found"); let slide_texts = focus_regions(&text); let slide_sections: Vec = slide_texts .iter() .map(|l| extract_slides(l.clone())) .collect(); - println!("test-cheatsheet {lang} {slide_sections:?}"); - // TODO - todo!(); - // 1. Get headers as # Header - // 2. Bunch slide_titles as ## Sections - // 3. Put them into SlideSections - // let file_name = format!("./training-slices/src/{lang}-cheatsheet.md"); - // let cheatsheet_text = read_to_string(file_name).expect("SUMMARY.md not found"); - // let cheatsheet_slide_texts = focus_regions(&cheatsheet_text); - // let cheatsheet_slide_sections: Vec = cheatsheet_slide_texts - // .iter() - // .map(|l| extract_slides(l.clone())) - // .collect(); - // Ok(()) + let file_name = format!("./training-slides/src/{lang}-cheatsheet.md"); + let cheatsheet_text = read_to_string(file_name).expect("lang-cheatsheet.md not found"); + let cheatsheet_lines = cheatsheet_text + .lines() + .filter(|l| l.starts_with("#")) + .map(|l| l.to_string()) + .collect::>(); + + let mut missing_files = false; + let mut idx = 0; + for line in cheatsheet_lines.iter() { + if line.starts_with("# ") { + if line != cheatsheet_lines.first().unwrap() { + idx += 1; + } + let header = line.strip_prefix("# ").unwrap(); + if header != slide_sections[idx].header { + eprintln!("{} header should be {}", line, slide_sections[idx].header); + missing_files = true; + } + } + if line.starts_with("## ") { + let slide_title = line + .strip_prefix("## ") + .expect("Expected the line to start with `## `"); + if !(slide_sections[idx].slide_titles).contains(&slide_title.to_string()) { + //println!("{:?}", &slide_sections[idx][1..]); + eprintln!( + "{} is not in {lang}-cheathseet.md under expected header {}", + slide_title, slide_sections[idx].header + ); + } + } + } + if missing_files { + panic!("You have missing slides"); + } else { + eprintln!("Neat! {lang}-cheatsheet.md is in sync"); + Ok(()) + } } fn write_cheatsheet(slide_sections: Vec) -> String { From 4d34c0f7d2cda6517757b559dea14351465edd3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Raz=20Guzm=C3=A1n=20Macedo?= Date: Wed, 12 Jun 2024 20:12:26 -0600 Subject: [PATCH 10/15] take out cruft --- xtask/src/tasks.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/xtask/src/tasks.rs b/xtask/src/tasks.rs index 6436124..d1844bb 100644 --- a/xtask/src/tasks.rs +++ b/xtask/src/tasks.rs @@ -14,7 +14,7 @@ fn get_slide_name(line: &str) -> String { assert!(line.starts_with("* [")); assert!(line.ends_with(".md)")); // SAFETY - // This line should be a well formed mdbook entries: `* [TEXT](./foo.md)` + // This line should be a well formed mdbook entry: `* [TEXT](./foo.md)` let top = line .rfind(']') .expect("the markdown file entry did not have a ']'"); @@ -166,23 +166,26 @@ pub fn make_cheatsheet(lang: &str) -> Result<(), eyre::Report> { let file_str = format!("./training-slides/src/{lang}-cheatsheet.md"); let new_file = Path::new(&file_str); - // If so, just check if headers any headers are missing - // Otherwise, create the new file, then write new file into `SUMMARY.md` + // If lang-cheatsheet.md exists, check if any headers are missing + // Otherwise, create the lang-cheatsheet.md match File::create_new(new_file) { Ok(mut f) => { let result_text = write_cheatsheet(slide_sections); - let _ = f.write_all(result_text.as_bytes()); - println!("Cheatsheat for {lang} written at {file_str}"); + drop(f.write_all(result_text.as_bytes())); + eprintln!("Cheatsheat for {lang} written at {file_str}"); + eprintln!("Make sure to add it to SUMMARY.md!") + } Err(_) => { - println!("File {lang}-cheatsheet.md already exists - checking it's in sync"); - let _ = test_cheatsheet(lang); + eprintln!("File {lang}-cheatsheet.md already exists - checking it's in sync"); + drop(test_cheatsheet(lang)); } } Ok(()) } pub fn test_cheatsheet(lang: &str) -> Result<(), eyre::Report> { + // Collect Vec let text = read_to_string("./training-slides/src/SUMMARY.md").expect("could not read_to_string - SUMMARY.md not found"); let slide_texts = focus_regions(&text); let slide_sections: Vec = slide_texts @@ -190,10 +193,13 @@ pub fn test_cheatsheet(lang: &str) -> Result<(), eyre::Report> { .map(|l| extract_slides(l.clone())) .collect(); + // Collect SlideSections and slide titles let file_name = format!("./training-slides/src/{lang}-cheatsheet.md"); let cheatsheet_text = read_to_string(file_name).expect("lang-cheatsheet.md not found"); let cheatsheet_lines = cheatsheet_text .lines() + // Tricky: We only care about entries that start with '#', so filtering for them is enough to get only the + // interesting lines! .filter(|l| l.starts_with("#")) .map(|l| l.to_string()) .collect::>(); @@ -216,11 +222,11 @@ pub fn test_cheatsheet(lang: &str) -> Result<(), eyre::Report> { .strip_prefix("## ") .expect("Expected the line to start with `## `"); if !(slide_sections[idx].slide_titles).contains(&slide_title.to_string()) { - //println!("{:?}", &slide_sections[idx][1..]); eprintln!( "{} is not in {lang}-cheathseet.md under expected header {}", slide_title, slide_sections[idx].header ); + missing_files = true; } } } From ddd8c1898de2c545b6a193af76077aeeee4a5578 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Raz=20Guzm=C3=A1n=20Macedo?= Date: Thu, 13 Jun 2024 07:44:16 -0600 Subject: [PATCH 11/15] add README.md for how cheatsheets work --- xtask/README.md | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 xtask/README.md diff --git a/xtask/README.md b/xtask/README.md new file mode 100644 index 0000000..9555c62 --- /dev/null +++ b/xtask/README.md @@ -0,0 +1,60 @@ +# Cheatsheets + +## Why + +Teaching Rust can be hard, and it's harder when trainees we teach come from different programming language backgrounds. + +A person in Python might have more questions about memory safety in general, since the GC allows them not to worry about that, but a person from C++ would be confused by the keyword `move` in a closure. + +To alleviate this, we've created some cheatsheets of the form + +``` +# Applied Rust + +## Methods and Traits +... +## Rust I/O Traits +... +## Generics +... +``` + +per programming language that match each slide we have in our normal syllabus with an entry on the second header (`## Methods and Traits`, for example) level. + +## How + +As `training-material` grows and changes, maintenance could be a nightmare. We basically don't want to be in the business of remember that a certain slide got reordered or moved from one section to another and thus needs changing in all the cheathseets as well. Therefore, this tool seeks to alleviate that with the following workflow: + +* call `cargo xtask make-cheatsheet python` at the root folder +* scrape Markdown headers in `SUMMARY.md` and segment topics by `Rust Fundamentals`, `Applied Rust` and `Advanced Rust` +* write out to `src/python-cheatsheet.md` if it doesn't exist +* if it does exist, check that it in sync: all headers in `python-cheatsheet.md` are in the appropriate sections, in order, and none are missing. + +Specifically, `make-cheatsheet` and `test-cheatsheet` are defined in `xtask/src/tasks.rs` with utility functions to take our `SUMMARY.md` + +``` +# Rust Fundamentals + * [Overview](./src/overview.md) + * [Installation](.src/installation.md) + * [Basic Types](./src/basic-types.md) +... +# Applied Rust + * [Methods and Traits](./src/methods-and-traits.md) + * [Rust I/O Traits](./src/rust-io-traits.md) + * [Generics](./src/generics.md) +``` + +and convert it into a `Vec`: + +``` + vec![SlideSection {header: "Rust Fundamentals", + slide_titles: vec!["Overview", "Installation", "Basic Types"]}, + SlideSection {header: "Applied Rust", + slide_titles: vec!["Methods and Traits", "Rust I/O Traits", "Generics"]}] +``` + +From there we can + +* create the cheatsheet for Python and have it written out to `training-slides/src/python-cheatsheet.md` by just iterating over `Vec` and prefixing with the appropriate header level before printing +* test that the cheathseet is in sync by scraping for all the lines that start with `#` in `python-cheatsheet.md` and check that they match, in order, those we scraped from `SUMMARY.md`. + From ce4e707bccd6dac2b47dfbbe53e70f9875c4ed45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Raz=20Guzm=C3=A1n=20Macedo?= Date: Thu, 13 Jun 2024 08:10:56 -0600 Subject: [PATCH 12/15] exclude example-code/ from workspace --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 915fa8d..d144870 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,4 +2,7 @@ members = [ "xtask", ] +exclude = ["example-code/*"] + resolver = "2" + From 54067a43b2ffc58c6bb38648ebd87a6d67a62753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Raz=20Guzm=C3=A1n=20Macedo?= Date: Thu, 13 Jun 2024 08:15:28 -0600 Subject: [PATCH 13/15] exclude example-code from workspace --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d144870..8da4b65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ members = [ "xtask", ] -exclude = ["example-code/*"] +exclude = ["example-code/"] resolver = "2" From ab2ef80b61b384e1f56625fa651761822038cd11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Raz=20Guzm=C3=A1n=20Macedo?= Date: Thu, 13 Jun 2024 08:38:44 -0600 Subject: [PATCH 14/15] add README note on additional sections at the end and code to allow extra sections --- xtask/README.md | 25 ++++++++++++++++++++++--- xtask/src/tasks.rs | 7 +++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/xtask/README.md b/xtask/README.md index 9555c62..37c5631 100644 --- a/xtask/README.md +++ b/xtask/README.md @@ -46,15 +46,34 @@ Specifically, `make-cheatsheet` and `test-cheatsheet` are defined in `xtask/src/ and convert it into a `Vec`: -``` +```rust vec![SlideSection {header: "Rust Fundamentals", slide_titles: vec!["Overview", "Installation", "Basic Types"]}, SlideSection {header: "Applied Rust", slide_titles: vec!["Methods and Traits", "Rust I/O Traits", "Generics"]}] ``` -From there we can +From there we can -* create the cheatsheet for Python and have it written out to `training-slides/src/python-cheatsheet.md` by just iterating over `Vec` and prefixing with the appropriate header level before printing +* create the cheatsheet for Python and have it written out to `training-slides/src/python-cheatsheet.md` by just iterating over `Vec` and prefixing with the appropriate header level before printing * test that the cheathseet is in sync by scraping for all the lines that start with `#` in `python-cheatsheet.md` and check that they match, in order, those we scraped from `SUMMARY.md`. +Note: some languages will warrant some special entries - any headers after the last `SlideSection` header will be ignored, +so that we can add additional relevant information without having to conform to the slides. + +Concretely, this is allowed: + +```md + +# Applied Rust +## Methods and Traits +## Rust I/O Traits +## Generics +# FAQ +## How to do... +# Syntax Clashes +## Operator overloading +... +``` + +but the code will signal if `# FAQ` or `# Syntax Clashes` appear before `# Applied Rust`. diff --git a/xtask/src/tasks.rs b/xtask/src/tasks.rs index d1844bb..1ad9d50 100644 --- a/xtask/src/tasks.rs +++ b/xtask/src/tasks.rs @@ -210,6 +210,13 @@ pub fn test_cheatsheet(lang: &str) -> Result<(), eyre::Report> { if line.starts_with("# ") { if line != cheatsheet_lines.first().unwrap() { idx += 1; + // Check if people have added extra headers - leave them alone + // so that lang - specific advice doesn't have to correlate to slides + // if it goes at the end + if idx == slide_sections.len() { + eprintln!("Neat! {lang}-cheatsheet.md is in sync AND contains some extra info at the end"); + return Ok(()); + } } let header = line.strip_prefix("# ").unwrap(); if header != slide_sections[idx].header { From d85c40fd5a366baabd8478416f055b65b99681da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Raz=20Guzm=C3=A1n=20Macedo?= Date: Mon, 1 Jul 2024 07:52:35 -0600 Subject: [PATCH 15/15] Update xtask/README.md Co-authored-by: Jonathan Pallant --- xtask/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xtask/README.md b/xtask/README.md index 37c5631..470c7bf 100644 --- a/xtask/README.md +++ b/xtask/README.md @@ -23,7 +23,7 @@ per programming language that match each slide we have in our normal syllabus wi ## How -As `training-material` grows and changes, maintenance could be a nightmare. We basically don't want to be in the business of remember that a certain slide got reordered or moved from one section to another and thus needs changing in all the cheathseets as well. Therefore, this tool seeks to alleviate that with the following workflow: +As `training-material` grows and changes, maintenance could be a nightmare. We basically don't want to be in the business of remembering that a certain slide got reordered or moved from one section to another and thus needs changing in all the cheatsheets as well. Therefore, this tool seeks to alleviate that with the following workflow: * call `cargo xtask make-cheatsheet python` at the root folder * scrape Markdown headers in `SUMMARY.md` and segment topics by `Rust Fundamentals`, `Applied Rust` and `Advanced Rust`