summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock9
-rw-r--r--Cargo.toml14
-rw-r--r--dev/Cargo.toml4
-rw-r--r--gen-dev-cargo-toml/Cargo.toml10
-rw-r--r--gen-dev-cargo-toml/src/main.rs68
-rw-r--r--src/dev.rs9
-rw-r--r--src/dev/check.rs81
-rw-r--r--src/dev/init.rs23
-rw-r--r--src/dev/update.rs53
-rw-r--r--src/exercise.rs5
-rw-r--r--src/init.rs41
-rw-r--r--src/main.rs27
-rw-r--r--tests/dev_cargo_bins.rs44
13 files changed, 182 insertions, 206 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 65d2216..4c95ca8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -312,15 +312,6 @@ dependencies = [
]
[[package]]
-name = "gen-dev-cargo-toml"
-version = "0.0.0"
-dependencies = [
- "anyhow",
- "serde",
- "toml_edit",
-]
-
-[[package]]
name = "hashbrown"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 06fbbf7..cde4182 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,9 +6,6 @@ exclude = [
"tests/fixture/success",
"dev",
]
-members = [
- "gen-dev-cargo-toml",
-]
[workspace.package]
version = "6.0.0-alpha.0"
@@ -20,11 +17,6 @@ authors = [
license = "MIT"
edition = "2021"
-[workspace.dependencies]
-anyhow = "1.0.82"
-serde = { version = "1.0.197", features = ["derive"] }
-toml_edit = { version = "0.22.9", default-features = false, features = ["parse", "serde"] }
-
[package]
name = "rustlings"
description = "Small exercises to get you used to reading and writing Rust code!"
@@ -42,15 +34,15 @@ include = [
]
[dependencies]
-anyhow.workspace = true
+anyhow = "1.0.82"
clap = { version = "4.5.4", features = ["derive"] }
crossterm = "0.27.0"
hashbrown = "0.14.3"
notify-debouncer-mini = "0.4.1"
ratatui = "0.26.2"
rustlings-macros = { path = "rustlings-macros", version = "6.0.0-alpha.0" }
-serde.workspace = true
-toml_edit.workspace = true
+serde = { version = "1.0.197", features = ["derive"] }
+toml_edit = { version = "0.22.9", default-features = false, features = ["parse", "serde"] }
which = "6.0.1"
[dev-dependencies]
diff --git a/dev/Cargo.toml b/dev/Cargo.toml
index 1d230eb..e66973e 100644
--- a/dev/Cargo.toml
+++ b/dev/Cargo.toml
@@ -1,6 +1,4 @@
-# This file is a hack to allow using `cargo run` to test `rustlings` during development.
-# You shouldn't edit it manually. It is created and updated by running `cargo run -p gen-dev-cargo-toml`.
-
+# Don't edit the `bin` list manually! It is updated by `cargo run -- dev update`
bin = [
{ name = "intro1", path = "../exercises/00_intro/intro1.rs" },
{ name = "intro2", path = "../exercises/00_intro/intro2.rs" },
diff --git a/gen-dev-cargo-toml/Cargo.toml b/gen-dev-cargo-toml/Cargo.toml
deleted file mode 100644
index 8922ae8..0000000
--- a/gen-dev-cargo-toml/Cargo.toml
+++ /dev/null
@@ -1,10 +0,0 @@
-[package]
-name = "gen-dev-cargo-toml"
-publish = false
-license.workspace = true
-edition.workspace = true
-
-[dependencies]
-anyhow.workspace = true
-serde.workspace = true
-toml_edit.workspace = true
diff --git a/gen-dev-cargo-toml/src/main.rs b/gen-dev-cargo-toml/src/main.rs
deleted file mode 100644
index 43b4ebd..0000000
--- a/gen-dev-cargo-toml/src/main.rs
+++ /dev/null
@@ -1,68 +0,0 @@
-// Generates `dev/Cargo.toml` such that it is synced with `info.toml`.
-// `dev/Cargo.toml` is a hack to allow using `cargo run` to test `rustlings`
-// during development.
-
-use anyhow::{bail, Context, Result};
-use serde::Deserialize;
-use std::{
- fs::{self, create_dir},
- io::ErrorKind,
-};
-
-#[derive(Deserialize)]
-struct ExerciseInfo {
- name: String,
- dir: Option<String>,
-}
-
-#[derive(Deserialize)]
-struct InfoFile {
- exercises: Vec<ExerciseInfo>,
-}
-
-fn main() -> Result<()> {
- let exercise_infos = toml_edit::de::from_str::<InfoFile>(
- &fs::read_to_string("info.toml").context("Failed to read `info.toml`")?,
- )
- .context("Failed to deserialize `info.toml`")?
- .exercises;
-
- let mut buf = Vec::with_capacity(1 << 14);
-
- buf.extend_from_slice(
- b"# This file is a hack to allow using `cargo run` to test `rustlings` during development.
-# You shouldn't edit it manually. It is created and updated by running `cargo run -p gen-dev-cargo-toml`.
-
-bin = [\n",
- );
-
- for exercise_info in exercise_infos {
- buf.extend_from_slice(b" { name = \"");
- buf.extend_from_slice(exercise_info.name.as_bytes());
- buf.extend_from_slice(b"\", path = \"../exercises/");
- if let Some(dir) = &exercise_info.dir {
- buf.extend_from_slice(dir.as_bytes());
- buf.push(b'/');
- }
- buf.extend_from_slice(exercise_info.name.as_bytes());
- buf.extend_from_slice(b".rs\" },\n");
- }
-
- buf.extend_from_slice(
- br#"]
-
-[package]
-name = "rustlings-dev"
-edition = "2021"
-publish = false
-"#,
- );
-
- if let Err(e) = create_dir("dev") {
- if e.kind() != ErrorKind::AlreadyExists {
- bail!("Failed to create the `dev` directory: {e}");
- }
- }
-
- fs::write("dev/Cargo.toml", buf).context("Failed to write `dev/Cargo.toml`")
-}
diff --git a/src/dev.rs b/src/dev.rs
index 7905f38..feca99e 100644
--- a/src/dev.rs
+++ b/src/dev.rs
@@ -1,22 +1,23 @@
use anyhow::{Context, Result};
use clap::Subcommand;
-use crate::info_file::InfoFile;
-
mod check;
mod init;
+mod update;
#[derive(Subcommand)]
pub enum DevCommands {
Init,
Check,
+ Update,
}
impl DevCommands {
- pub fn run(self, info_file: InfoFile) -> Result<()> {
+ pub fn run(self) -> Result<()> {
match self {
DevCommands::Init => init::init().context(INIT_ERR),
- DevCommands::Check => check::check(info_file),
+ DevCommands::Check => check::check(),
+ DevCommands::Update => update::update(),
}
}
}
diff --git a/src/dev/check.rs b/src/dev/check.rs
index 5910a75..bc8e459 100644
--- a/src/dev/check.rs
+++ b/src/dev/check.rs
@@ -1,16 +1,83 @@
+use anyhow::{bail, Context, Result};
use std::fs;
-use anyhow::{Context, Result};
+use crate::{
+ info_file::{ExerciseInfo, InfoFile},
+ DEVELOPING_OFFIFICAL_RUSTLINGS,
+};
-use crate::{info_file::InfoFile, init::cargo_toml};
+pub fn bins_start_end_ind(cargo_toml: &str) -> Result<(usize, usize)> {
+ let start_ind = cargo_toml
+ .find("bin = [")
+ .context("Failed to find the start of the `bin` list (`bin = [`)")?
+ + 7;
+ let end_ind = start_ind
+ + cargo_toml
+ .get(start_ind..)
+ .and_then(|slice| slice.as_bytes().iter().position(|c| *c == b']'))
+ .context("Failed to find the end of the `bin` list (`]`)")?;
+
+ Ok((start_ind, end_ind))
+}
+
+pub fn append_bins(
+ buf: &mut Vec<u8>,
+ exercise_infos: &[ExerciseInfo],
+ exercise_path_prefix: &[u8],
+) {
+ buf.push(b'\n');
+ for exercise_info in exercise_infos {
+ buf.extend_from_slice(b" { name = \"");
+ buf.extend_from_slice(exercise_info.name.as_bytes());
+ buf.extend_from_slice(b"\", path = \"");
+ buf.extend_from_slice(exercise_path_prefix);
+ buf.extend_from_slice(b"exercises/");
+ if let Some(dir) = &exercise_info.dir {
+ buf.extend_from_slice(dir.as_bytes());
+ buf.push(b'/');
+ }
+ buf.extend_from_slice(exercise_info.name.as_bytes());
+ buf.extend_from_slice(b".rs\" },\n");
+ }
+}
+
+fn check_cargo_toml(
+ exercise_infos: &[ExerciseInfo],
+ current_cargo_toml: &str,
+ exercise_path_prefix: &[u8],
+) -> Result<()> {
+ let (bins_start_ind, bins_end_ind) = bins_start_end_ind(current_cargo_toml)?;
+
+ let old_bins = &current_cargo_toml.as_bytes()[bins_start_ind..bins_end_ind];
+ let mut new_bins = Vec::with_capacity(1 << 13);
+ append_bins(&mut new_bins, exercise_infos, exercise_path_prefix);
+
+ if old_bins != new_bins {
+ bail!("`Cargo.toml` is outdated. Run `rustlings dev update` to update it");
+ }
+
+ Ok(())
+}
+
+pub fn check() -> Result<()> {
+ let info_file = InfoFile::parse()?;
-pub fn check(info_file: InfoFile) -> Result<()> {
// TODO: Add checks
- // TODO: Keep dependencies!
- fs::write("Cargo.toml", cargo_toml(&info_file.exercises))
- .context("Failed to update the file `Cargo.toml`")?;
- println!("Updated `Cargo.toml`");
+ if DEVELOPING_OFFIFICAL_RUSTLINGS {
+ check_cargo_toml(
+ &info_file.exercises,
+ include_str!("../../dev/Cargo.toml"),
+ b"../",
+ )
+ .context("The file `dev/Cargo.toml` is outdated. Please run `cargo run -- dev update` to update it")?;
+ } else {
+ let current_cargo_toml =
+ fs::read_to_string("Cargo.toml").context("Failed to read the file `Cargo.toml`")?;
+ check_cargo_toml(&info_file.exercises, &current_cargo_toml, b"").context(
+ "The file `Cargo.toml` is outdated. Please run `rustlings dev update` to update it",
+ )?;
+ }
println!("\nEverything looks fine!");
diff --git a/src/dev/init.rs b/src/dev/init.rs
index 0993522..3ce5055 100644
--- a/src/dev/init.rs
+++ b/src/dev/init.rs
@@ -1,6 +1,5 @@
-use std::fs::{self, create_dir};
-
use anyhow::{Context, Result};
+use std::fs::{self, create_dir};
use crate::CURRENT_FORMAT_VERSION;
@@ -19,11 +18,8 @@ pub fn init() -> Result<()> {
)
.context("Failed to create the file `rustlings/info.toml`")?;
- fs::write(
- "rustlings/Cargo.toml",
- format!("{CARGO_TOML_COMMENT}{}", crate::init::CARGO_TOML_PACKAGE),
- )
- .context("Failed to create the file `rustlings/Cargo.toml`")?;
+ fs::write("rustlings/Cargo.toml", CARGO_TOML)
+ .context("Failed to create the file `rustlings/Cargo.toml`")?;
fs::write("rustlings/.gitignore", crate::init::GITIGNORE)
.context("Failed to create the file `rustlings/.gitignore`")?;
@@ -80,10 +76,17 @@ mode = "test"
hint = """???"""
"#;
-const CARGO_TOML_COMMENT: &str =
- "# You shouldn't edit this file manually! It is updated by `rustlings dev check`
+const CARGO_TOML: &[u8] =
+ br#"# Don't edit the `bin` list manually! It is updated by `rustlings dev update`
+bin = []
-";
+[package]
+name = "rustlings"
+edition = "2021"
+publish = false
+
+[dependencies]
+"#;
const README: &str = "# Rustlings 🦀
diff --git a/src/dev/update.rs b/src/dev/update.rs
new file mode 100644
index 0000000..981934d
--- /dev/null
+++ b/src/dev/update.rs
@@ -0,0 +1,53 @@
+use std::fs;
+
+use anyhow::{Context, Result};
+
+use crate::{
+ info_file::{ExerciseInfo, InfoFile},
+ DEVELOPING_OFFIFICAL_RUSTLINGS,
+};
+
+use super::check::{append_bins, bins_start_end_ind};
+
+fn update_cargo_toml(
+ exercise_infos: &[ExerciseInfo],
+ current_cargo_toml: &str,
+ cargo_toml_path: &str,
+ exercise_path_prefix: &[u8],
+) -> Result<()> {
+ let (bins_start_ind, bins_end_ind) = bins_start_end_ind(current_cargo_toml)?;
+
+ let mut new_cargo_toml = Vec::with_capacity(1 << 13);
+ new_cargo_toml.extend_from_slice(current_cargo_toml[..bins_start_ind].as_bytes());
+ append_bins(&mut new_cargo_toml, exercise_infos, exercise_path_prefix);
+ new_cargo_toml.extend_from_slice(current_cargo_toml[bins_end_ind..].as_bytes());
+
+ fs::write(cargo_toml_path, new_cargo_toml).context("Failed to write the `Cargo.toml` file")?;
+
+ Ok(())
+}
+
+pub fn update() -> Result<()> {
+ let info_file = InfoFile::parse()?;
+
+ if DEVELOPING_OFFIFICAL_RUSTLINGS {
+ update_cargo_toml(
+ &info_file.exercises,
+ include_str!("../../dev/Cargo.toml"),
+ "dev/Cargo.toml",
+ b"../",
+ )
+ .context("Failed to update the file `dev/Cargo.toml`")?;
+
+ println!("Updated `dev/Cargo.toml`");
+ } else {
+ let current_cargo_toml =
+ fs::read_to_string("Cargo.toml").context("Failed to read the file `Cargo.toml`")?;
+ update_cargo_toml(&info_file.exercises, &current_cargo_toml, "Cargo.toml", b"")
+ .context("Failed to update the file `Cargo.toml`")?;
+
+ println!("Updated `Cargo.toml`");
+ }
+
+ Ok(())
+}
diff --git a/src/exercise.rs b/src/exercise.rs
index 8bdf399..c4df999 100644
--- a/src/exercise.rs
+++ b/src/exercise.rs
@@ -9,6 +9,7 @@ use std::{
use crate::{
embedded::{WriteStrategy, EMBEDDED_FILES},
info_file::Mode,
+ DEVELOPING_OFFIFICAL_RUSTLINGS,
};
pub struct TerminalFileLink<'a> {
@@ -50,9 +51,7 @@ impl Exercise {
cmd.arg(command);
// A hack to make `cargo run` work when developing Rustlings.
- // Use `dev/Cargo.toml` when in the directory of the repository.
- #[cfg(debug_assertions)]
- if std::path::Path::new("tests").exists() {
+ if DEVELOPING_OFFIFICAL_RUSTLINGS {
cmd.arg("--manifest-path").arg("dev/Cargo.toml");
}
diff --git a/src/init.rs b/src/init.rs
index 5fa44d4..52315e2 100644
--- a/src/init.rs
+++ b/src/init.rs
@@ -6,30 +6,19 @@ use std::{
path::Path,
};
-use crate::{embedded::EMBEDDED_FILES, info_file::ExerciseInfo};
-
-pub fn cargo_toml(exercise_infos: &[ExerciseInfo]) -> Vec<u8> {
- let mut cargo_toml = Vec::with_capacity(1 << 13);
- cargo_toml.extend_from_slice(b"bin = [\n");
- for exercise_info in exercise_infos {
- cargo_toml.extend_from_slice(b" { name = \"");
- cargo_toml.extend_from_slice(exercise_info.name.as_bytes());
- cargo_toml.extend_from_slice(b"\", path = \"exercises/");
- if let Some(dir) = &exercise_info.dir {
- cargo_toml.extend_from_slice(dir.as_bytes());
- cargo_toml.push(b'/');
- }
- cargo_toml.extend_from_slice(exercise_info.name.as_bytes());
- cargo_toml.extend_from_slice(b".rs\" },\n");
+use crate::embedded::EMBEDDED_FILES;
+
+const CARGO_TOML: &[u8] = {
+ let cargo_toml = include_bytes!("../dev/Cargo.toml");
+ // Skip the first line (comment).
+ let mut start_ind = 0;
+ while cargo_toml[start_ind] != b'\n' {
+ start_ind += 1;
}
+ cargo_toml.split_at(start_ind + 1).1
+};
- cargo_toml.extend_from_slice(b"]\n\n");
- cargo_toml.extend_from_slice(CARGO_TOML_PACKAGE.as_bytes());
-
- cargo_toml
-}
-
-pub fn init(exercise_infos: &[ExerciseInfo]) -> Result<()> {
+pub fn init() -> Result<()> {
if Path::new("exercises").is_dir() && Path::new("Cargo.toml").is_file() {
bail!(PROBABLY_IN_RUSTLINGS_DIR_ERR);
}
@@ -49,7 +38,7 @@ pub fn init(exercise_infos: &[ExerciseInfo]) -> Result<()> {
.init_exercises_dir()
.context("Failed to initialize the `rustlings/exercises` directory")?;
- fs::write("Cargo.toml", cargo_toml(exercise_infos))
+ fs::write("Cargo.toml", CARGO_TOML)
.context("Failed to create the file `rustlings/Cargo.toml`")?;
fs::write(".gitignore", GITIGNORE)
@@ -64,12 +53,6 @@ pub fn init(exercise_infos: &[ExerciseInfo]) -> Result<()> {
Ok(())
}
-pub const CARGO_TOML_PACKAGE: &str = r#"[package]
-name = "rustlings"
-edition = "2021"
-publish = false
-"#;
-
pub const GITIGNORE: &[u8] = b"Cargo.lock
.rustlings-state.txt
target
diff --git a/src/main.rs b/src/main.rs
index 8b3f28f..ea5f7c9 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -25,6 +25,17 @@ mod watch;
use self::{app_state::AppState, dev::DevCommands, info_file::InfoFile, watch::WatchExit};
const CURRENT_FORMAT_VERSION: u8 = 1;
+const DEVELOPING_OFFIFICAL_RUSTLINGS: bool = {
+ #[allow(unused_assignments, unused_mut)]
+ let mut debug_profile = false;
+
+ #[cfg(debug_assertions)]
+ {
+ debug_profile = true;
+ }
+
+ debug_profile
+};
/// Rustlings is a collection of small exercises to get you used to writing and reading Rust code
#[derive(Parser)]
@@ -66,17 +77,11 @@ fn main() -> Result<()> {
which::which("cargo").context(CARGO_NOT_FOUND_ERR)?;
- let info_file = InfoFile::parse()?;
-
- if info_file.format_version > CURRENT_FORMAT_VERSION {
- bail!(FORMAT_VERSION_HIGHER_ERR);
- }
-
match args.command {
Some(Subcommands::Init) => {
- return init::init(&info_file.exercises).context("Initialization failed");
+ return init::init().context("Initialization failed");
}
- Some(Subcommands::Dev(dev_command)) => return dev_command.run(info_file),
+ Some(Subcommands::Dev(dev_command)) => return dev_command.run(),
_ => (),
}
@@ -85,6 +90,12 @@ fn main() -> Result<()> {
exit(1);
}
+ let info_file = InfoFile::parse()?;
+
+ if info_file.format_version > CURRENT_FORMAT_VERSION {
+ bail!(FORMAT_VERSION_HIGHER_ERR);
+ }
+
let (mut app_state, state_file_status) = AppState::new(
info_file.exercises,
info_file.final_message.unwrap_or_default(),
diff --git a/tests/dev_cargo_bins.rs b/tests/dev_cargo_bins.rs
deleted file mode 100644
index 81f48b1..0000000
--- a/tests/dev_cargo_bins.rs
+++ /dev/null
@@ -1,44 +0,0 @@
-// Makes sure that `dev/Cargo.toml` is synced with `info.toml`.
-// When this test fails, you just need to run `cargo run -p gen-dev-cargo-toml`.
-
-use serde::Deserialize;
-use std::fs;
-
-#[derive(Deserialize)]
-struct ExerciseInfo {
- name: String,
- dir: Option<String>,
-}
-
-#[derive(Deserialize)]
-struct InfoFile {
- exercises: Vec<ExerciseInfo>,
-}
-
-#[test]
-fn dev_cargo_bins() {
- let cargo_toml = fs::read_to_string("dev/Cargo.toml").unwrap();
-
- let exercise_infos =
- toml_edit::de::from_str::<InfoFile>(&fs::read_to_string("info.toml").unwrap())
- .unwrap()
- .exercises;
-
- let mut start_ind = 0;
- for exercise_info in exercise_infos {
- let name_start = start_ind + cargo_toml[start_ind..].find('"').unwrap() + 1;
- let name_end = name_start + cargo_toml[name_start..].find('"').unwrap();
- assert_eq!(exercise_info.name, &cargo_toml[name_start..name_end]);
-
- let path_start = name_end + cargo_toml[name_end + 1..].find('"').unwrap() + 2;
- let path_end = path_start + cargo_toml[path_start..].find('"').unwrap();
- let expected_path = if let Some(dir) = exercise_info.dir {
- format!("../exercises/{dir}/{}.rs", exercise_info.name)
- } else {
- format!("../exercises/{}.rs", exercise_info.name)
- };
- assert_eq!(expected_path, &cargo_toml[path_start..path_end]);
-
- start_ind = path_end + 1;
- }
-}