summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authormo8it <mo8it@proton.me>2024-04-17 15:55:50 +0200
committermo8it <mo8it@proton.me>2024-04-17 15:55:50 +0200
commit501b973c258a3c2e3a463d58c16402302184380f (patch)
treea03812f5d3f5a5ef9e171077fb6aa6571a3f31fb /src
parent30636e7cf345757f95235744ff81376ae81c9aa2 (diff)
Add "dev update"
Diffstat (limited to 'src')
-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
7 files changed, 178 insertions, 61 deletions
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(),