mirror of
https://github.com/rust-lang/rustlings.git
synced 2025-01-09 20:03:24 +03:00
Compare commits
No commits in common. "86684b7fc9dd5e8bedad6056565645d1d980925c" and "a2be6754bf2833371fe99ddc2d01c0d518f8eb27" have entirely different histories.
86684b7fc9
...
a2be6754bf
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,7 +1,7 @@
|
||||||
# Cargo
|
# Cargo
|
||||||
target/
|
target/
|
||||||
Cargo.lock
|
/tests/fixture/*/Cargo.lock
|
||||||
!/Cargo.lock
|
/dev/Cargo.lock
|
||||||
|
|
||||||
# State file
|
# State file
|
||||||
.rustlings-state.txt
|
.rustlings-state.txt
|
||||||
|
|
16
Cargo.lock
generated
16
Cargo.lock
generated
|
@ -656,9 +656,9 @@ checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.33"
|
version = "0.38.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e3cc72858054fcff6d7dea32df2aeaee6a7c24227366d7ea429aada2f26b16ad"
|
checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.5.0",
|
"bitflags 2.5.0",
|
||||||
"errno",
|
"errno",
|
||||||
|
@ -771,9 +771,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.2"
|
version = "1.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
|
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
@ -830,9 +830,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.60"
|
version = "2.0.59"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
|
checksum = "4a6531ffc7b071655e4ce2e04bd464c4830bb585a61cabb96cf808f05172615a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -856,9 +856,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.22.12"
|
version = "0.22.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef"
|
checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
@ -42,7 +42,7 @@ notify-debouncer-mini = "0.4.1"
|
||||||
ratatui = "0.26.2"
|
ratatui = "0.26.2"
|
||||||
rustlings-macros = { path = "rustlings-macros", version = "6.0.0-alpha.0" }
|
rustlings-macros = { path = "rustlings-macros", version = "6.0.0-alpha.0" }
|
||||||
serde = { version = "1.0.198", features = ["derive"] }
|
serde = { version = "1.0.198", features = ["derive"] }
|
||||||
toml_edit = { version = "0.22.12", default-features = false, features = ["parse", "serde"] }
|
toml_edit = { version = "0.22.9", default-features = false, features = ["parse", "serde"] }
|
||||||
which = "6.0.1"
|
which = "6.0.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Don't edit the `bin` list manually! It is updated by `cargo run -- dev update`. This comment line will be stripped in `rustlings init`.
|
# Don't edit the `bin` list manually! It is updated by `cargo run -- dev update`
|
||||||
bin = [
|
bin = [
|
||||||
{ name = "intro1", path = "../exercises/00_intro/intro1.rs" },
|
{ name = "intro1", path = "../exercises/00_intro/intro1.rs" },
|
||||||
{ name = "intro2", path = "../exercises/00_intro/intro2.rs" },
|
{ name = "intro2", path = "../exercises/00_intro/intro2.rs" },
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
This file is used to check if the user tries to run Rustlings in the repository (the method before v6)
|
|
|
@ -53,8 +53,7 @@ impl AppState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// See `Self::write` for more information about the file format.
|
// See `Self::write` for more information about the file format.
|
||||||
let mut lines = self.file_buf.split(|c| *c == b'\n').skip(2);
|
let mut lines = self.file_buf.split(|c| *c == b'\n');
|
||||||
|
|
||||||
let Some(current_exercise_name) = lines.next() else {
|
let Some(current_exercise_name) = lines.next() else {
|
||||||
return StateFileStatus::NotRead;
|
return StateFileStatus::NotRead;
|
||||||
};
|
};
|
||||||
|
@ -301,17 +300,13 @@ impl AppState {
|
||||||
|
|
||||||
// Write the state file.
|
// Write the state file.
|
||||||
// The file's format is very simple:
|
// The file's format is very simple:
|
||||||
// - The first line is a comment.
|
// - The first line is the name of the current exercise. It must end with `\n` even if there
|
||||||
// - The second line is an empty line.
|
|
||||||
// - The third line is the name of the current exercise. It must end with `\n` even if there
|
|
||||||
// are no done exercises.
|
// are no done exercises.
|
||||||
// - The fourth line is an empty line.
|
// - The second line is an empty line.
|
||||||
// - All remaining lines are the names of done exercises.
|
// - All remaining lines are the names of done exercises.
|
||||||
fn write(&mut self) -> Result<()> {
|
fn write(&mut self) -> Result<()> {
|
||||||
self.file_buf.clear();
|
self.file_buf.clear();
|
||||||
|
|
||||||
self.file_buf
|
|
||||||
.extend_from_slice(b"DON'T EDIT THIS FILE!\n\n");
|
|
||||||
self.file_buf
|
self.file_buf
|
||||||
.extend_from_slice(self.current_exercise().name.as_bytes());
|
.extend_from_slice(self.current_exercise().name.as_bytes());
|
||||||
self.file_buf.push(b'\n');
|
self.file_buf.push(b'\n');
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
use anyhow::{Context, Result};
|
|
||||||
|
|
||||||
use crate::info_file::ExerciseInfo;
|
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn updated_cargo_toml(
|
|
||||||
exercise_infos: &[ExerciseInfo],
|
|
||||||
current_cargo_toml: &str,
|
|
||||||
exercise_path_prefix: &[u8],
|
|
||||||
) -> Result<Vec<u8>> {
|
|
||||||
let (bins_start_ind, bins_end_ind) = bins_start_end_ind(current_cargo_toml)?;
|
|
||||||
|
|
||||||
let mut updated_cargo_toml = Vec::with_capacity(1 << 13);
|
|
||||||
updated_cargo_toml.extend_from_slice(current_cargo_toml[..bins_start_ind].as_bytes());
|
|
||||||
append_bins(
|
|
||||||
&mut updated_cargo_toml,
|
|
||||||
exercise_infos,
|
|
||||||
exercise_path_prefix,
|
|
||||||
);
|
|
||||||
updated_cargo_toml.extend_from_slice(current_cargo_toml[bins_end_ind..].as_bytes());
|
|
||||||
|
|
||||||
Ok(updated_cargo_toml)
|
|
||||||
}
|
|
25
src/dev.rs
25
src/dev.rs
|
@ -1,39 +1,28 @@
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
|
|
||||||
use crate::DEBUG_PROFILE;
|
use crate::DEVELOPING_OFFICIAL_RUSTLINGS;
|
||||||
|
|
||||||
mod check;
|
mod check;
|
||||||
mod new;
|
mod init;
|
||||||
mod update;
|
mod update;
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
pub enum DevCommands {
|
pub enum DevCommands {
|
||||||
/// Create a new project for third-party Rustlings exercises
|
Init,
|
||||||
New {
|
|
||||||
/// The path to create the project in
|
|
||||||
path: PathBuf,
|
|
||||||
/// Don't initialize a Git repository in the project directory
|
|
||||||
#[arg(long)]
|
|
||||||
no_git: bool,
|
|
||||||
},
|
|
||||||
/// Run checks on the exercises
|
|
||||||
Check,
|
Check,
|
||||||
/// Update the `Cargo.toml` file for the exercises
|
|
||||||
Update,
|
Update,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DevCommands {
|
impl DevCommands {
|
||||||
pub fn run(self) -> Result<()> {
|
pub fn run(self) -> Result<()> {
|
||||||
match self {
|
match self {
|
||||||
DevCommands::New { path, no_git } => {
|
DevCommands::Init => {
|
||||||
if DEBUG_PROFILE {
|
if DEVELOPING_OFFICIAL_RUSTLINGS {
|
||||||
bail!("Disabled in the debug build");
|
bail!("Disabled while developing the official Rustlings");
|
||||||
}
|
}
|
||||||
|
|
||||||
new::new(&path, no_git).context(INIT_ERR)
|
init::init().context(INIT_ERR)
|
||||||
}
|
}
|
||||||
DevCommands::Check => check::check(),
|
DevCommands::Check => check::check(),
|
||||||
DevCommands::Update => update::update(),
|
DevCommands::Update => update::update(),
|
||||||
|
|
|
@ -7,9 +7,8 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cargo_toml::{append_bins, bins_start_end_ind},
|
|
||||||
info_file::{ExerciseInfo, InfoFile},
|
info_file::{ExerciseInfo, InfoFile},
|
||||||
CURRENT_FORMAT_VERSION, DEBUG_PROFILE,
|
CURRENT_FORMAT_VERSION, DEVELOPING_OFFICIAL_RUSTLINGS,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn forbidden_char(input: &str) -> Option<char> {
|
fn forbidden_char(input: &str) -> Option<char> {
|
||||||
|
@ -137,6 +136,41 @@ fn check_exercises(info_file: &InfoFile) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
fn check_cargo_toml(
|
||||||
exercise_infos: &[ExerciseInfo],
|
exercise_infos: &[ExerciseInfo],
|
||||||
current_cargo_toml: &str,
|
current_cargo_toml: &str,
|
||||||
|
@ -149,13 +183,7 @@ fn check_cargo_toml(
|
||||||
append_bins(&mut new_bins, exercise_infos, exercise_path_prefix);
|
append_bins(&mut new_bins, exercise_infos, exercise_path_prefix);
|
||||||
|
|
||||||
if old_bins != new_bins {
|
if old_bins != new_bins {
|
||||||
if DEBUG_PROFILE {
|
bail!("`Cargo.toml` is outdated. Run `rustlings dev update` to update it");
|
||||||
bail!("The file `dev/Cargo.toml` is outdated. Please run `cargo run -- dev update` to update it");
|
|
||||||
} else {
|
|
||||||
bail!(
|
|
||||||
"The file `Cargo.toml` is outdated. Please run `rustlings dev update` to update it",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -165,16 +193,19 @@ pub fn check() -> Result<()> {
|
||||||
let info_file = InfoFile::parse()?;
|
let info_file = InfoFile::parse()?;
|
||||||
check_exercises(&info_file)?;
|
check_exercises(&info_file)?;
|
||||||
|
|
||||||
if DEBUG_PROFILE {
|
if DEVELOPING_OFFICIAL_RUSTLINGS {
|
||||||
check_cargo_toml(
|
check_cargo_toml(
|
||||||
&info_file.exercises,
|
&info_file.exercises,
|
||||||
include_str!("../../dev/Cargo.toml"),
|
include_str!("../../dev/Cargo.toml"),
|
||||||
b"../",
|
b"../",
|
||||||
)?;
|
)
|
||||||
|
.context("The file `dev/Cargo.toml` is outdated. Please run `cargo run -- dev update` to update it")?;
|
||||||
} else {
|
} else {
|
||||||
let current_cargo_toml =
|
let current_cargo_toml =
|
||||||
fs::read_to_string("Cargo.toml").context("Failed to read the file `Cargo.toml`")?;
|
fs::read_to_string("Cargo.toml").context("Failed to read the file `Cargo.toml`")?;
|
||||||
check_cargo_toml(&info_file.exercises, ¤t_cargo_toml, b"")?;
|
check_cargo_toml(&info_file.exercises, ¤t_cargo_toml, b"").context(
|
||||||
|
"The file `Cargo.toml` is outdated. Please run `rustlings dev update` to update it",
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nEverything looks fine!");
|
println!("\nEverything looks fine!");
|
||||||
|
|
|
@ -1,73 +1,41 @@
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use std::{
|
use std::fs::{self, create_dir};
|
||||||
env::set_current_dir,
|
|
||||||
fs::{self, create_dir},
|
|
||||||
path::Path,
|
|
||||||
process::Command,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::CURRENT_FORMAT_VERSION;
|
use crate::CURRENT_FORMAT_VERSION;
|
||||||
|
|
||||||
fn create_rel_dir(dir_name: &str, current_dir: &str) -> Result<()> {
|
pub fn init() -> Result<()> {
|
||||||
create_dir(dir_name)
|
create_dir("rustlings").context("Failed to create the directory `rustlings`")?;
|
||||||
.with_context(|| format!("Failed to create the directory {current_dir}/{dir_name}"))?;
|
|
||||||
println!("Created the directory {current_dir}/{dir_name}");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_rel_file<C>(file_name: &str, current_dir: &str, content: C) -> Result<()>
|
create_dir("rustlings/exercises")
|
||||||
where
|
.context("Failed to create the directory `rustlings/exercises`")?;
|
||||||
C: AsRef<[u8]>,
|
|
||||||
{
|
|
||||||
fs::write(file_name, content)
|
|
||||||
.with_context(|| format!("Failed to create the file {current_dir}/{file_name}"))?;
|
|
||||||
// Space to align with `create_rel_dir`.
|
|
||||||
println!("Created the file {current_dir}/{file_name}");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(path: &Path, no_git: bool) -> Result<()> {
|
create_dir("rustlings/solutions")
|
||||||
let dir_name = path.to_string_lossy();
|
.context("Failed to create the directory `rustlings/solutions`")?;
|
||||||
|
|
||||||
create_dir(path).with_context(|| format!("Failed to create the directory {dir_name}"))?;
|
fs::write(
|
||||||
println!("Created the directory {dir_name}");
|
"rustlings/info.toml",
|
||||||
|
|
||||||
set_current_dir(path)
|
|
||||||
.with_context(|| format!("Failed to set {dir_name} as the current directory"))?;
|
|
||||||
|
|
||||||
if !no_git
|
|
||||||
&& !Command::new("git")
|
|
||||||
.arg("init")
|
|
||||||
.status()
|
|
||||||
.context("Failed to run `git init`")?
|
|
||||||
.success()
|
|
||||||
{
|
|
||||||
bail!("`git init` didn't run successfully. See the error message above");
|
|
||||||
}
|
|
||||||
|
|
||||||
write_rel_file(".gitignore", &dir_name, crate::init::GITIGNORE)?;
|
|
||||||
|
|
||||||
create_rel_dir("exercises", &dir_name)?;
|
|
||||||
create_rel_dir("solutions", &dir_name)?;
|
|
||||||
|
|
||||||
write_rel_file(
|
|
||||||
"info.toml",
|
|
||||||
&dir_name,
|
|
||||||
format!("{INFO_FILE_BEFORE_FORMAT_VERSION}{CURRENT_FORMAT_VERSION}{INFO_FILE_AFTER_FORMAT_VERSION}"),
|
format!("{INFO_FILE_BEFORE_FORMAT_VERSION}{CURRENT_FORMAT_VERSION}{INFO_FILE_AFTER_FORMAT_VERSION}"),
|
||||||
)?;
|
)
|
||||||
|
.context("Failed to create the file `rustlings/info.toml`")?;
|
||||||
|
|
||||||
write_rel_file("Cargo.toml", &dir_name, CARGO_TOML)?;
|
fs::write("rustlings/Cargo.toml", CARGO_TOML)
|
||||||
|
.context("Failed to create the file `rustlings/Cargo.toml`")?;
|
||||||
|
|
||||||
write_rel_file("README.md", &dir_name, README)?;
|
fs::write("rustlings/.gitignore", crate::init::GITIGNORE)
|
||||||
|
.context("Failed to create the file `rustlings/.gitignore`")?;
|
||||||
|
|
||||||
create_rel_dir(".vscode", &dir_name)?;
|
fs::write("rustlings/README.md", README)
|
||||||
write_rel_file(
|
.context("Failed to create the file `rustlings/README.md`")?;
|
||||||
".vscode/extensions.json",
|
|
||||||
&dir_name,
|
create_dir("rustlings/.vscode")
|
||||||
|
.context("Failed to create the directory `rustlings/.vscode`")?;
|
||||||
|
fs::write(
|
||||||
|
"rustlings/.vscode/extensions.json",
|
||||||
crate::init::VS_CODE_EXTENSIONS_JSON,
|
crate::init::VS_CODE_EXTENSIONS_JSON,
|
||||||
)?;
|
)
|
||||||
|
.context("Failed to create the file `rustlings/.vscode/extensions.json`")?;
|
||||||
|
|
||||||
println!("\nInitialization done ✓");
|
println!("{INIT_DONE}");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -129,3 +97,13 @@ First,
|
||||||
|
|
||||||
Then, open your terminal in this directory and run `rustlings` to get started with the exercises 🚀
|
Then, open your terminal in this directory and run `rustlings` to get started with the exercises 🚀
|
||||||
";
|
";
|
||||||
|
|
||||||
|
const INIT_DONE: &str = r#"Initialization done!
|
||||||
|
You can start developing third-party Rustlings exercises in the `rustlings` directory :D
|
||||||
|
|
||||||
|
If the initialization was done in a Rust project which is a Cargo workspace, you need to add the
|
||||||
|
path to the `rustlings` directory to the `workspace.exclude` list in the project's `Cargo.toml`
|
||||||
|
file. For example:
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
exclude = ["rustlings"]"#;
|
|
@ -3,22 +3,26 @@ use std::fs;
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cargo_toml::updated_cargo_toml,
|
|
||||||
info_file::{ExerciseInfo, InfoFile},
|
info_file::{ExerciseInfo, InfoFile},
|
||||||
DEBUG_PROFILE,
|
DEVELOPING_OFFICIAL_RUSTLINGS,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::check::{append_bins, bins_start_end_ind};
|
||||||
|
|
||||||
fn update_cargo_toml(
|
fn update_cargo_toml(
|
||||||
exercise_infos: &[ExerciseInfo],
|
exercise_infos: &[ExerciseInfo],
|
||||||
current_cargo_toml: &str,
|
current_cargo_toml: &str,
|
||||||
exercise_path_prefix: &[u8],
|
|
||||||
cargo_toml_path: &str,
|
cargo_toml_path: &str,
|
||||||
|
exercise_path_prefix: &[u8],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let updated_cargo_toml =
|
let (bins_start_ind, bins_end_ind) = bins_start_end_ind(current_cargo_toml)?;
|
||||||
updated_cargo_toml(exercise_infos, current_cargo_toml, exercise_path_prefix)?;
|
|
||||||
|
|
||||||
fs::write(cargo_toml_path, updated_cargo_toml)
|
let mut new_cargo_toml = Vec::with_capacity(1 << 13);
|
||||||
.context("Failed to write the `Cargo.toml` file")?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -26,12 +30,12 @@ fn update_cargo_toml(
|
||||||
pub fn update() -> Result<()> {
|
pub fn update() -> Result<()> {
|
||||||
let info_file = InfoFile::parse()?;
|
let info_file = InfoFile::parse()?;
|
||||||
|
|
||||||
if DEBUG_PROFILE {
|
if DEVELOPING_OFFICIAL_RUSTLINGS {
|
||||||
update_cargo_toml(
|
update_cargo_toml(
|
||||||
&info_file.exercises,
|
&info_file.exercises,
|
||||||
include_str!("../../dev/Cargo.toml"),
|
include_str!("../../dev/Cargo.toml"),
|
||||||
b"../",
|
|
||||||
"dev/Cargo.toml",
|
"dev/Cargo.toml",
|
||||||
|
b"../",
|
||||||
)
|
)
|
||||||
.context("Failed to update the file `dev/Cargo.toml`")?;
|
.context("Failed to update the file `dev/Cargo.toml`")?;
|
||||||
|
|
||||||
|
@ -39,7 +43,7 @@ pub fn update() -> Result<()> {
|
||||||
} else {
|
} else {
|
||||||
let current_cargo_toml =
|
let current_cargo_toml =
|
||||||
fs::read_to_string("Cargo.toml").context("Failed to read the file `Cargo.toml`")?;
|
fs::read_to_string("Cargo.toml").context("Failed to read the file `Cargo.toml`")?;
|
||||||
update_cargo_toml(&info_file.exercises, ¤t_cargo_toml, b"", "Cargo.toml")
|
update_cargo_toml(&info_file.exercises, ¤t_cargo_toml, "Cargo.toml", b"")
|
||||||
.context("Failed to update the file `Cargo.toml`")?;
|
.context("Failed to update the file `Cargo.toml`")?;
|
||||||
|
|
||||||
println!("Updated `Cargo.toml`");
|
println!("Updated `Cargo.toml`");
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::{
|
||||||
process::{Command, Output},
|
process::{Command, Output},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{info_file::Mode, DEBUG_PROFILE};
|
use crate::{info_file::Mode, DEVELOPING_OFFICIAL_RUSTLINGS};
|
||||||
|
|
||||||
pub struct TerminalFileLink<'a> {
|
pub struct TerminalFileLink<'a> {
|
||||||
path: &'a str,
|
path: &'a str,
|
||||||
|
@ -48,7 +48,7 @@ impl Exercise {
|
||||||
cmd.arg(command);
|
cmd.arg(command);
|
||||||
|
|
||||||
// A hack to make `cargo run` work when developing Rustlings.
|
// A hack to make `cargo run` work when developing Rustlings.
|
||||||
if DEBUG_PROFILE && Path::new("tests").exists() {
|
if DEVELOPING_OFFICIAL_RUSTLINGS && Path::new("tests").exists() {
|
||||||
cmd.arg("--manifest-path").arg("dev/Cargo.toml");
|
cmd.arg("--manifest-path").arg("dev/Cargo.toml");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
26
src/init.rs
26
src/init.rs
|
@ -6,7 +6,17 @@ use std::{
|
||||||
path::Path,
|
path::Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{cargo_toml::updated_cargo_toml, embedded::EMBEDDED_FILES, info_file::InfoFile};
|
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
|
||||||
|
};
|
||||||
|
|
||||||
pub fn init() -> Result<()> {
|
pub fn init() -> Result<()> {
|
||||||
if Path::new("exercises").is_dir() && Path::new("Cargo.toml").is_file() {
|
if Path::new("exercises").is_dir() && Path::new("Cargo.toml").is_file() {
|
||||||
|
@ -28,19 +38,7 @@ pub fn init() -> Result<()> {
|
||||||
.init_exercises_dir()
|
.init_exercises_dir()
|
||||||
.context("Failed to initialize the `rustlings/exercises` directory")?;
|
.context("Failed to initialize the `rustlings/exercises` directory")?;
|
||||||
|
|
||||||
let info_file = InfoFile::parse()?;
|
fs::write("Cargo.toml", CARGO_TOML)
|
||||||
let current_cargo_toml = include_str!("../dev/Cargo.toml");
|
|
||||||
// Skip the first line (comment).
|
|
||||||
let newline_ind = current_cargo_toml
|
|
||||||
.as_bytes()
|
|
||||||
.iter()
|
|
||||||
.position(|c| *c == b'\n')
|
|
||||||
.context("The embedded `Cargo.toml` is empty or contains only one line.")?;
|
|
||||||
let current_cargo_toml =
|
|
||||||
¤t_cargo_toml[(newline_ind + 1).min(current_cargo_toml.len() - 1)..];
|
|
||||||
let updated_cargo_toml = updated_cargo_toml(&info_file.exercises, current_cargo_toml, b"")
|
|
||||||
.context("Failed to generate `Cargo.toml`")?;
|
|
||||||
fs::write("Cargo.toml", updated_cargo_toml)
|
|
||||||
.context("Failed to create the file `rustlings/Cargo.toml`")?;
|
.context("Failed to create the file `rustlings/Cargo.toml`")?;
|
||||||
|
|
||||||
fs::write(".gitignore", GITIGNORE)
|
fs::write(".gitignore", GITIGNORE)
|
||||||
|
|
29
src/main.rs
29
src/main.rs
|
@ -14,7 +14,6 @@ use std::{
|
||||||
use self::{app_state::AppState, dev::DevCommands, info_file::InfoFile, watch::WatchExit};
|
use self::{app_state::AppState, dev::DevCommands, info_file::InfoFile, watch::WatchExit};
|
||||||
|
|
||||||
mod app_state;
|
mod app_state;
|
||||||
mod cargo_toml;
|
|
||||||
mod dev;
|
mod dev;
|
||||||
mod embedded;
|
mod embedded;
|
||||||
mod exercise;
|
mod exercise;
|
||||||
|
@ -26,7 +25,7 @@ mod run;
|
||||||
mod watch;
|
mod watch;
|
||||||
|
|
||||||
const CURRENT_FORMAT_VERSION: u8 = 1;
|
const CURRENT_FORMAT_VERSION: u8 = 1;
|
||||||
const DEBUG_PROFILE: bool = {
|
const DEVELOPING_OFFICIAL_RUSTLINGS: bool = {
|
||||||
#[allow(unused_assignments, unused_mut)]
|
#[allow(unused_assignments, unused_mut)]
|
||||||
let mut debug_profile = false;
|
let mut debug_profile = false;
|
||||||
|
|
||||||
|
@ -54,7 +53,7 @@ struct Args {
|
||||||
enum Subcommands {
|
enum Subcommands {
|
||||||
/// Initialize Rustlings
|
/// Initialize Rustlings
|
||||||
Init,
|
Init,
|
||||||
/// Run a single exercise. Runs the next pending exercise if the exercise name is not specified
|
/// Run a single exercise. Runs the next pending exercise if the exercise name is not specified.
|
||||||
Run {
|
Run {
|
||||||
/// The name of the exercise
|
/// The name of the exercise
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
|
@ -64,12 +63,11 @@ enum Subcommands {
|
||||||
/// The name of the exercise
|
/// The name of the exercise
|
||||||
name: String,
|
name: String,
|
||||||
},
|
},
|
||||||
/// Show a hint. Shows the hint of the next pending exercise if the exercise name is not specified
|
/// Show a hint. Shows the hint of the next pending exercise if the exercise name is not specified.
|
||||||
Hint {
|
Hint {
|
||||||
/// The name of the exercise
|
/// The name of the exercise
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
},
|
},
|
||||||
/// Commands for developing (third-party) Rustlings exercises
|
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
Dev(DevCommands),
|
Dev(DevCommands),
|
||||||
}
|
}
|
||||||
|
@ -77,24 +75,12 @@ enum Subcommands {
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
if !DEBUG_PROFILE && Path::new("dev/rustlings-repo.txt").exists() {
|
|
||||||
bail!("{OLD_METHOD_ERR}");
|
|
||||||
}
|
|
||||||
|
|
||||||
which::which("cargo").context(CARGO_NOT_FOUND_ERR)?;
|
which::which("cargo").context(CARGO_NOT_FOUND_ERR)?;
|
||||||
|
|
||||||
match args.command {
|
match args.command {
|
||||||
Some(Subcommands::Init) => {
|
Some(Subcommands::Init) => {
|
||||||
if DEBUG_PROFILE {
|
if DEVELOPING_OFFICIAL_RUSTLINGS {
|
||||||
bail!("Disabled in the debug build");
|
bail!("Disabled while developing the official Rustlings");
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut stdout = io::stdout().lock();
|
|
||||||
stdout.write_all(b"This command will create the directory `rustlings/` which will contain the exercises.\nPress ENTER to continue ")?;
|
|
||||||
stdout.flush()?;
|
|
||||||
io::stdin().lock().read_until(b'\n', &mut Vec::new())?;
|
|
||||||
stdout.write_all(b"\n")?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return init::init().context("Initialization failed");
|
return init::init().context("Initialization failed");
|
||||||
|
@ -188,11 +174,6 @@ fn main() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
const OLD_METHOD_ERR: &str = "You are trying to run Rustlings using the old method before v6.
|
|
||||||
The new method doesn't include cloning the Rustlings' repository.
|
|
||||||
Please follow the instructions in the README:
|
|
||||||
https://github.com/rust-lang/rustlings#getting-started";
|
|
||||||
|
|
||||||
const CARGO_NOT_FOUND_ERR: &str = "Failed to find `cargo`.
|
const CARGO_NOT_FOUND_ERR: &str = "Failed to find `cargo`.
|
||||||
Did you already install Rust?
|
Did you already install Rust?
|
||||||
Try running `cargo --version` to diagnose the problem.";
|
Try running `cargo --version` to diagnose the problem.";
|
||||||
|
|
Loading…
Reference in a new issue