summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock8
-rw-r--r--Cargo.toml21
-rw-r--r--rustlings-macros/Cargo.toml12
-rw-r--r--rustlings-macros/src/lib.rs95
-rw-r--r--src/main.rs23
5 files changed, 152 insertions, 7 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f4853d0..e432072 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -574,6 +574,7 @@ dependencies = [
"indicatif",
"notify-debouncer-mini",
"predicates",
+ "rustlings-macros",
"serde",
"serde_json",
"shlex",
@@ -583,6 +584,13 @@ dependencies = [
]
[[package]]
+name = "rustlings-macros"
+version = "5.6.1"
+dependencies = [
+ "quote",
+]
+
+[[package]]
name = "ryu"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 2d152cf..e08be8b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,19 +1,30 @@
-[package]
-name = "rustlings"
-description = "Small exercises to get you used to reading and writing Rust code!"
+[workspace]
+resolver = "2"
+
+[workspace.package]
version = "5.6.1"
authors = [
"Liv <mokou@fastmail.com>",
"Carol (Nichols || Goulding) <carol.nichols@gmail.com>",
]
+license = "MIT"
edition = "2021"
+[package]
+name = "rustlings"
+description = "Small exercises to get you used to reading and writing Rust code!"
+version.workspace = true
+authors.workspace = true
+license.workspace = true
+edition.workspace = true
+
[dependencies]
anyhow = "1.0.81"
clap = { version = "4.5.4", features = ["derive"] }
console = "0.15.8"
indicatif = "0.17.8"
notify-debouncer-mini = "0.4.1"
+rustlings-macros = { path = "rustlings-macros" }
serde_json = "1.0.115"
serde = { version = "1.0.197", features = ["derive"] }
shlex = "1.3.0"
@@ -21,10 +32,6 @@ toml_edit = { version = "0.22.9", default-features = false, features = ["parse",
which = "6.0.1"
winnow = "0.6.5"
-[[bin]]
-name = "rustlings"
-path = "src/main.rs"
-
[dev-dependencies]
assert_cmd = "2.0.14"
glob = "0.3.0"
diff --git a/rustlings-macros/Cargo.toml b/rustlings-macros/Cargo.toml
new file mode 100644
index 0000000..0114c8f
--- /dev/null
+++ b/rustlings-macros/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "rustlings-macros"
+version.workspace = true
+authors.workspace = true
+license.workspace = true
+edition.workspace = true
+
+[lib]
+proc-macro = true
+
+[dependencies]
+quote = "1.0.35"
diff --git a/rustlings-macros/src/lib.rs b/rustlings-macros/src/lib.rs
new file mode 100644
index 0000000..dd1a588
--- /dev/null
+++ b/rustlings-macros/src/lib.rs
@@ -0,0 +1,95 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use std::{fs::read_dir, panic, path::PathBuf};
+
+fn path_to_string(path: PathBuf) -> String {
+ path.into_os_string()
+ .into_string()
+ .unwrap_or_else(|original| {
+ panic!("The path {} is invalid UTF8", original.to_string_lossy());
+ })
+}
+
+#[proc_macro]
+pub fn include_files(_: TokenStream) -> TokenStream {
+ let mut files = Vec::with_capacity(8);
+ let mut dirs = Vec::with_capacity(128);
+
+ for entry in read_dir("exercises").expect("Failed to open the exercises directory") {
+ let entry = entry.expect("Failed to read the exercises directory");
+
+ if entry.file_type().unwrap().is_file() {
+ let path = entry.path();
+ if path.file_name().unwrap() != "README.md" {
+ files.push(path_to_string(path));
+ }
+
+ continue;
+ }
+
+ let dir_path = entry.path();
+ let dir_files = read_dir(&dir_path).unwrap_or_else(|e| {
+ panic!("Failed to open the directory {}: {e}", dir_path.display());
+ });
+ let dir_path = path_to_string(dir_path);
+ let dir_files = dir_files.filter_map(|entry| {
+ let entry = entry.unwrap_or_else(|e| {
+ panic!("Failed to read the directory {dir_path}: {e}");
+ });
+ let path = entry.path();
+
+ if !entry.file_type().unwrap().is_file() {
+ panic!("Found {} but expected only files", path.display());
+ }
+
+ if path.file_name().unwrap() == "README.md" {
+ return None;
+ }
+
+ if path.extension() != Some("rs".as_ref()) {
+ panic!(
+ "Found {} but expected only README.md and .rs files",
+ path.display(),
+ );
+ }
+
+ Some(path_to_string(path))
+ });
+
+ dirs.push(quote! {
+ EmbeddedFlatDir {
+ path: #dir_path,
+ readme: EmbeddedFile {
+ path: concat!(#dir_path, "/README.md"),
+ content: ::std::include_bytes!(concat!("../", #dir_path, "/README.md")),
+ },
+ content: vec![
+ #(EmbeddedFile {
+ path: #dir_files,
+ content: ::std::include_bytes!(concat!("../", #dir_files)),
+ }),*
+ ],
+ }
+ });
+ }
+
+ quote! {
+ EmbeddedFiles {
+ info_toml_content: ::std::include_str!("../info.toml"),
+ exercises_dir: ExercisesDir {
+ readme: EmbeddedFile {
+ path: "exercises/README.md",
+ content: ::std::include_bytes!("../exercises/README.md"),
+ },
+ files: vec![#(
+ EmbeddedFile {
+ path: #files,
+ content: ::std::include_bytes!(concat!("../", #files)),
+ }
+ ),*],
+ dirs: vec![#(#dirs),*],
+ },
+ }
+ }
+ .into()
+}
diff --git a/src/main.rs b/src/main.rs
index 8f73dbb..fed8c11 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -27,6 +27,28 @@ mod project;
mod run;
mod verify;
+struct EmbeddedFile {
+ path: &'static str,
+ content: &'static [u8],
+}
+
+struct EmbeddedFlatDir {
+ path: &'static str,
+ readme: EmbeddedFile,
+ content: Vec<EmbeddedFile>,
+}
+
+struct ExercisesDir {
+ readme: EmbeddedFile,
+ files: Vec<EmbeddedFile>,
+ dirs: Vec<EmbeddedFlatDir>,
+}
+
+struct EmbeddedFiles {
+ info_toml_content: &'static str,
+ exercises_dir: ExercisesDir,
+}
+
/// Rustlings is a collection of small exercises to get you used to writing and reading Rust code
#[derive(Parser)]
#[command(version)]
@@ -87,6 +109,7 @@ enum Subcommands {
}
fn main() -> Result<()> {
+ let embedded_files = rustlings_macros::include_files!();
let args = Args::parse();
if args.command.is_none() {