mirror of
https://github.com/rust-lang/rustlings.git
synced 2025-01-09 20:03:24 +03:00
Compare commits
11 commits
a2be6754bf
...
86684b7fc9
Author | SHA1 | Date | |
---|---|---|---|
86684b7fc9 | |||
4ce2714da1 | |||
e93a99e19e | |||
61a84a2c11 | |||
30040d7778 | |||
e3b9124b85 | |||
642c3bd37e | |||
49e4a1fab0 | |||
04d36996dd | |||
f1a60780b9 | |||
d83c91edc6 |
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,7 +1,7 @@
|
||||||
# Cargo
|
# Cargo
|
||||||
target/
|
target/
|
||||||
/tests/fixture/*/Cargo.lock
|
Cargo.lock
|
||||||
/dev/Cargo.lock
|
!/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.32"
|
version = "0.38.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89"
|
checksum = "e3cc72858054fcff6d7dea32df2aeaee6a7c24227366d7ea429aada2f26b16ad"
|
||||||
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.1"
|
version = "1.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
|
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
@ -830,9 +830,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.59"
|
version = "2.0.60"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4a6531ffc7b071655e4ce2e04bd464c4830bb585a61cabb96cf808f05172615a"
|
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -856,9 +856,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.22.9"
|
version = "0.22.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4"
|
checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef"
|
||||||
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.9", default-features = false, features = ["parse", "serde"] }
|
toml_edit = { version = "0.22.12", 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`
|
# Don't edit the `bin` list manually! It is updated by `cargo run -- dev update`. This comment line will be stripped in `rustlings init`.
|
||||||
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
dev/rustlings-repo.txt
Normal file
1
dev/rustlings-repo.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
This file is used to check if the user tries to run Rustlings in the repository (the method before v6)
|
|
@ -53,7 +53,8 @@ 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');
|
let mut lines = self.file_buf.split(|c| *c == b'\n').skip(2);
|
||||||
|
|
||||||
let Some(current_exercise_name) = lines.next() else {
|
let Some(current_exercise_name) = lines.next() else {
|
||||||
return StateFileStatus::NotRead;
|
return StateFileStatus::NotRead;
|
||||||
};
|
};
|
||||||
|
@ -300,13 +301,17 @@ 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 the name of the current exercise. It must end with `\n` even if there
|
// - The first line is a comment.
|
||||||
// are no done exercises.
|
|
||||||
// - The second line is an empty line.
|
// - 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.
|
||||||
|
// - The fourth 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');
|
||||||
|
|
57
src/cargo_toml.rs
Normal file
57
src/cargo_toml.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
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,28 +1,39 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
|
|
||||||
use crate::DEVELOPING_OFFICIAL_RUSTLINGS;
|
use crate::DEBUG_PROFILE;
|
||||||
|
|
||||||
mod check;
|
mod check;
|
||||||
mod init;
|
mod new;
|
||||||
mod update;
|
mod update;
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
pub enum DevCommands {
|
pub enum DevCommands {
|
||||||
Init,
|
/// Create a new project for third-party Rustlings exercises
|
||||||
|
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::Init => {
|
DevCommands::New { path, no_git } => {
|
||||||
if DEVELOPING_OFFICIAL_RUSTLINGS {
|
if DEBUG_PROFILE {
|
||||||
bail!("Disabled while developing the official Rustlings");
|
bail!("Disabled in the debug build");
|
||||||
}
|
}
|
||||||
|
|
||||||
init::init().context(INIT_ERR)
|
new::new(&path, no_git).context(INIT_ERR)
|
||||||
}
|
}
|
||||||
DevCommands::Check => check::check(),
|
DevCommands::Check => check::check(),
|
||||||
DevCommands::Update => update::update(),
|
DevCommands::Update => update::update(),
|
||||||
|
|
|
@ -7,8 +7,9 @@ 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, DEVELOPING_OFFICIAL_RUSTLINGS,
|
CURRENT_FORMAT_VERSION, DEBUG_PROFILE,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn forbidden_char(input: &str) -> Option<char> {
|
fn forbidden_char(input: &str) -> Option<char> {
|
||||||
|
@ -136,41 +137,6 @@ 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,
|
||||||
|
@ -183,7 +149,13 @@ 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 {
|
||||||
bail!("`Cargo.toml` is outdated. Run `rustlings dev update` to update it");
|
if DEBUG_PROFILE {
|
||||||
|
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(())
|
||||||
|
@ -193,19 +165,16 @@ pub fn check() -> Result<()> {
|
||||||
let info_file = InfoFile::parse()?;
|
let info_file = InfoFile::parse()?;
|
||||||
check_exercises(&info_file)?;
|
check_exercises(&info_file)?;
|
||||||
|
|
||||||
if DEVELOPING_OFFICIAL_RUSTLINGS {
|
if DEBUG_PROFILE {
|
||||||
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"").context(
|
check_cargo_toml(&info_file.exercises, ¤t_cargo_toml, b"")?;
|
||||||
"The file `Cargo.toml` is outdated. Please run `rustlings dev update` to update it",
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nEverything looks fine!");
|
println!("\nEverything looks fine!");
|
||||||
|
|
|
@ -1,41 +1,73 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use std::fs::{self, create_dir};
|
use std::{
|
||||||
|
env::set_current_dir,
|
||||||
|
fs::{self, create_dir},
|
||||||
|
path::Path,
|
||||||
|
process::Command,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::CURRENT_FORMAT_VERSION;
|
use crate::CURRENT_FORMAT_VERSION;
|
||||||
|
|
||||||
pub fn init() -> Result<()> {
|
fn create_rel_dir(dir_name: &str, current_dir: &str) -> Result<()> {
|
||||||
create_dir("rustlings").context("Failed to create the directory `rustlings`")?;
|
create_dir(dir_name)
|
||||||
|
.with_context(|| format!("Failed to create the directory {current_dir}/{dir_name}"))?;
|
||||||
|
println!("Created the directory {current_dir}/{dir_name}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
create_dir("rustlings/exercises")
|
fn write_rel_file<C>(file_name: &str, current_dir: &str, content: C) -> Result<()>
|
||||||
.context("Failed to create the directory `rustlings/exercises`")?;
|
where
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
create_dir("rustlings/solutions")
|
pub fn new(path: &Path, no_git: bool) -> Result<()> {
|
||||||
.context("Failed to create the directory `rustlings/solutions`")?;
|
let dir_name = path.to_string_lossy();
|
||||||
|
|
||||||
fs::write(
|
create_dir(path).with_context(|| format!("Failed to create the directory {dir_name}"))?;
|
||||||
"rustlings/info.toml",
|
println!("Created the directory {dir_name}");
|
||||||
|
|
||||||
|
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`")?;
|
|
||||||
|
|
||||||
fs::write("rustlings/Cargo.toml", CARGO_TOML)
|
write_rel_file("Cargo.toml", &dir_name, CARGO_TOML)?;
|
||||||
.context("Failed to create the file `rustlings/Cargo.toml`")?;
|
|
||||||
|
|
||||||
fs::write("rustlings/.gitignore", crate::init::GITIGNORE)
|
write_rel_file("README.md", &dir_name, README)?;
|
||||||
.context("Failed to create the file `rustlings/.gitignore`")?;
|
|
||||||
|
|
||||||
fs::write("rustlings/README.md", README)
|
create_rel_dir(".vscode", &dir_name)?;
|
||||||
.context("Failed to create the file `rustlings/README.md`")?;
|
write_rel_file(
|
||||||
|
".vscode/extensions.json",
|
||||||
create_dir("rustlings/.vscode")
|
&dir_name,
|
||||||
.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!("{INIT_DONE}");
|
println!("\nInitialization done ✓");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -97,13 +129,3 @@ 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,26 +3,22 @@ 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},
|
||||||
DEVELOPING_OFFICIAL_RUSTLINGS,
|
DEBUG_PROFILE,
|
||||||
};
|
};
|
||||||
|
|
||||||
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,
|
||||||
cargo_toml_path: &str,
|
|
||||||
exercise_path_prefix: &[u8],
|
exercise_path_prefix: &[u8],
|
||||||
|
cargo_toml_path: &str,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let (bins_start_ind, bins_end_ind) = bins_start_end_ind(current_cargo_toml)?;
|
let updated_cargo_toml =
|
||||||
|
updated_cargo_toml(exercise_infos, current_cargo_toml, exercise_path_prefix)?;
|
||||||
|
|
||||||
let mut new_cargo_toml = Vec::with_capacity(1 << 13);
|
fs::write(cargo_toml_path, updated_cargo_toml)
|
||||||
new_cargo_toml.extend_from_slice(current_cargo_toml[..bins_start_ind].as_bytes());
|
.context("Failed to write the `Cargo.toml` file")?;
|
||||||
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(())
|
||||||
}
|
}
|
||||||
|
@ -30,12 +26,12 @@ fn update_cargo_toml(
|
||||||
pub fn update() -> Result<()> {
|
pub fn update() -> Result<()> {
|
||||||
let info_file = InfoFile::parse()?;
|
let info_file = InfoFile::parse()?;
|
||||||
|
|
||||||
if DEVELOPING_OFFICIAL_RUSTLINGS {
|
if DEBUG_PROFILE {
|
||||||
update_cargo_toml(
|
update_cargo_toml(
|
||||||
&info_file.exercises,
|
&info_file.exercises,
|
||||||
include_str!("../../dev/Cargo.toml"),
|
include_str!("../../dev/Cargo.toml"),
|
||||||
"dev/Cargo.toml",
|
|
||||||
b"../",
|
b"../",
|
||||||
|
"dev/Cargo.toml",
|
||||||
)
|
)
|
||||||
.context("Failed to update the file `dev/Cargo.toml`")?;
|
.context("Failed to update the file `dev/Cargo.toml`")?;
|
||||||
|
|
||||||
|
@ -43,7 +39,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, "Cargo.toml", b"")
|
update_cargo_toml(&info_file.exercises, ¤t_cargo_toml, b"", "Cargo.toml")
|
||||||
.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, DEVELOPING_OFFICIAL_RUSTLINGS};
|
use crate::{info_file::Mode, DEBUG_PROFILE};
|
||||||
|
|
||||||
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 DEVELOPING_OFFICIAL_RUSTLINGS && Path::new("tests").exists() {
|
if DEBUG_PROFILE && 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,17 +6,7 @@ use std::{
|
||||||
path::Path,
|
path::Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::embedded::EMBEDDED_FILES;
|
use crate::{cargo_toml::updated_cargo_toml, embedded::EMBEDDED_FILES, info_file::InfoFile};
|
||||||
|
|
||||||
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() {
|
||||||
|
@ -38,7 +28,19 @@ 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")?;
|
||||||
|
|
||||||
fs::write("Cargo.toml", CARGO_TOML)
|
let info_file = InfoFile::parse()?;
|
||||||
|
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,6 +14,7 @@ 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;
|
||||||
|
@ -25,7 +26,7 @@ mod run;
|
||||||
mod watch;
|
mod watch;
|
||||||
|
|
||||||
const CURRENT_FORMAT_VERSION: u8 = 1;
|
const CURRENT_FORMAT_VERSION: u8 = 1;
|
||||||
const DEVELOPING_OFFICIAL_RUSTLINGS: bool = {
|
const DEBUG_PROFILE: bool = {
|
||||||
#[allow(unused_assignments, unused_mut)]
|
#[allow(unused_assignments, unused_mut)]
|
||||||
let mut debug_profile = false;
|
let mut debug_profile = false;
|
||||||
|
|
||||||
|
@ -53,7 +54,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>,
|
||||||
|
@ -63,11 +64,12 @@ 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),
|
||||||
}
|
}
|
||||||
|
@ -75,12 +77,24 @@ 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 DEVELOPING_OFFICIAL_RUSTLINGS {
|
if DEBUG_PROFILE {
|
||||||
bail!("Disabled while developing the official Rustlings");
|
bail!("Disabled in the debug build");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
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");
|
||||||
|
@ -174,6 +188,11 @@ 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