mirror of
https://github.com/rust-lang/rustlings.git
synced 2024-12-26 15:26:29 +00:00
Implement third-party exercises trust handling
This commit is contained in:
parent
c613b70363
commit
15ca847c37
5 changed files with 198 additions and 12 deletions
80
Cargo.lock
generated
80
Cargo.lock
generated
|
@ -253,6 +253,27 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs"
|
||||||
|
version = "5.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
|
||||||
|
dependencies = [
|
||||||
|
"dirs-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs-sys"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"option-ext",
|
||||||
|
"redox_users",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "doc-comment"
|
name = "doc-comment"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
@ -320,6 +341,17 @@ dependencies = [
|
||||||
"toml_edit",
|
"toml_edit",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.14.3"
|
version = "0.14.3"
|
||||||
|
@ -428,6 +460,16 @@ version = "0.2.153"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libredox"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.5.0",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.4.13"
|
version = "0.4.13"
|
||||||
|
@ -528,6 +570,12 @@ version = "1.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "option-ext"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
|
@ -634,6 +682,17 @@ dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_users"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"libredox",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.10.4"
|
version = "1.10.4"
|
||||||
|
@ -684,6 +743,7 @@ dependencies = [
|
||||||
"assert_cmd",
|
"assert_cmd",
|
||||||
"clap",
|
"clap",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
|
"dirs",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
"notify-debouncer-mini",
|
"notify-debouncer-mini",
|
||||||
"predicates",
|
"predicates",
|
||||||
|
@ -865,6 +925,26 @@ version = "0.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
|
checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.58"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.58"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.58",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.6.5"
|
version = "0.6.5"
|
||||||
|
|
|
@ -37,6 +37,7 @@ edition.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
clap = { version = "4.5.4", features = ["derive"] }
|
clap = { version = "4.5.4", features = ["derive"] }
|
||||||
crossterm = "0.27.0"
|
crossterm = "0.27.0"
|
||||||
|
dirs = "5.0.1"
|
||||||
hashbrown = "0.14.3"
|
hashbrown = "0.14.3"
|
||||||
notify-debouncer-mini = "0.4.1"
|
notify-debouncer-mini = "0.4.1"
|
||||||
ratatui = "0.26.1"
|
ratatui = "0.26.1"
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::{
|
||||||
path::Path,
|
path::Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{embedded::EMBEDDED_FILES, info_file::ExerciseInfo};
|
use crate::{embedded::EMBEDDED_FILES, info_file::ExerciseInfo, trust::trust_current_dir};
|
||||||
|
|
||||||
fn create_cargo_toml(exercise_infos: &[ExerciseInfo]) -> io::Result<()> {
|
fn create_cargo_toml(exercise_infos: &[ExerciseInfo]) -> io::Result<()> {
|
||||||
let mut cargo_toml = Vec::with_capacity(1 << 13);
|
let mut cargo_toml = Vec::with_capacity(1 << 13);
|
||||||
|
@ -85,6 +85,8 @@ pub fn init(exercise_infos: &[ExerciseInfo]) -> Result<()> {
|
||||||
|
|
||||||
create_vscode_dir().context("Failed to create the file `rustlings/.vscode/extensions.json`")?;
|
create_vscode_dir().context("Failed to create the file `rustlings/.vscode/extensions.json`")?;
|
||||||
|
|
||||||
|
trust_current_dir()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
53
src/main.rs
53
src/main.rs
|
@ -1,5 +1,4 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use app_state::StateFileStatus;
|
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
terminal::{Clear, ClearType},
|
terminal::{Clear, ClearType},
|
||||||
|
@ -19,14 +18,16 @@ mod init;
|
||||||
mod list;
|
mod list;
|
||||||
mod progress_bar;
|
mod progress_bar;
|
||||||
mod run;
|
mod run;
|
||||||
|
mod trust;
|
||||||
mod watch;
|
mod watch;
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
app_state::AppState,
|
app_state::{AppState, StateFileStatus},
|
||||||
info_file::InfoFile,
|
info_file::InfoFile,
|
||||||
init::init,
|
init::init,
|
||||||
list::list,
|
list::list,
|
||||||
run::run,
|
run::run,
|
||||||
|
trust::{current_dir_is_trusted, trust_current_dir},
|
||||||
watch::{watch, WatchExit},
|
watch::{watch, WatchExit},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -61,6 +62,11 @@ enum Subcommands {
|
||||||
/// The name of the exercise
|
/// The name of the exercise
|
||||||
name: String,
|
name: String,
|
||||||
},
|
},
|
||||||
|
/// Trust the current directory with its exercises.
|
||||||
|
///
|
||||||
|
/// You only need to run this if you want to work on third-party exercises or after you moved
|
||||||
|
/// the official exercises that were initialized with `rustlings init`.
|
||||||
|
Trust,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
|
@ -72,14 +78,26 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
if matches!(args.command, Some(Subcommands::Init)) {
|
if matches!(args.command, Some(Subcommands::Init)) {
|
||||||
init(&info_file.exercises).context("Initialization failed")?;
|
init(&info_file.exercises).context("Initialization failed")?;
|
||||||
|
|
||||||
println!("{POST_INIT_MSG}");
|
println!("{POST_INIT_MSG}");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else if !Path::new("exercises").is_dir() {
|
}
|
||||||
|
|
||||||
|
if !Path::new("exercises").is_dir() {
|
||||||
println!("{PRE_INIT_MSG}");
|
println!("{PRE_INIT_MSG}");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if matches!(args.command, Some(Subcommands::Trust)) {
|
||||||
|
trust_current_dir()?;
|
||||||
|
println!("{POST_TRUST_MSG}");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !current_dir_is_trusted()? {
|
||||||
|
println!("{NOT_TRUSTED_MSG}");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
let (mut app_state, state_file_status) = AppState::new(
|
let (mut app_state, state_file_status) = AppState::new(
|
||||||
info_file.exercises,
|
info_file.exercises,
|
||||||
info_file.final_message.unwrap_or_default(),
|
info_file.final_message.unwrap_or_default(),
|
||||||
|
@ -130,8 +148,6 @@ fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// `Init` is handled above.
|
|
||||||
Some(Subcommands::Init) => (),
|
|
||||||
Some(Subcommands::Run { name }) => {
|
Some(Subcommands::Run { name }) => {
|
||||||
if let Some(name) = name {
|
if let Some(name) = name {
|
||||||
app_state.set_current_exercise_by_name(&name)?;
|
app_state.set_current_exercise_by_name(&name)?;
|
||||||
|
@ -149,6 +165,8 @@ fn main() -> Result<()> {
|
||||||
app_state.set_current_exercise_by_name(&name)?;
|
app_state.set_current_exercise_by_name(&name)?;
|
||||||
println!("{}", app_state.current_exercise().hint);
|
println!("{}", app_state.current_exercise().hint);
|
||||||
}
|
}
|
||||||
|
// `Init` and `Trust` are handled above.
|
||||||
|
Some(Subcommands::Init | Subcommands::Trust) => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -158,8 +176,13 @@ 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.";
|
||||||
|
|
||||||
|
const POST_INIT_MSG: &str = "Done initialization!
|
||||||
|
|
||||||
|
Run `cd rustlings` to go into the generated directory.
|
||||||
|
Then run `rustlings` to get started.";
|
||||||
|
|
||||||
const PRE_INIT_MSG: &str = r"
|
const PRE_INIT_MSG: &str = r"
|
||||||
welcome to...
|
Welcome to...
|
||||||
_ _ _
|
_ _ _
|
||||||
_ __ _ _ ___| |_| (_)_ __ __ _ ___
|
_ __ _ _ ___| |_| (_)_ __ __ _ ___
|
||||||
| '__| | | / __| __| | | '_ \ / _` / __|
|
| '__| | | / __| __| | | '_ \ / _` / __|
|
||||||
|
@ -170,11 +193,19 @@ const PRE_INIT_MSG: &str = r"
|
||||||
The `exercises` directory wasn't found in the current directory.
|
The `exercises` directory wasn't found in the current directory.
|
||||||
If you are just starting with Rustlings, run the command `rustlings init` to initialize it.";
|
If you are just starting with Rustlings, run the command `rustlings init` to initialize it.";
|
||||||
|
|
||||||
const POST_INIT_MSG: &str = "
|
const POST_TRUST_MSG: &str = "You now trust the exercises in the current directory.
|
||||||
Done initialization!
|
Run `rustlings` to start working on them.";
|
||||||
|
|
||||||
Run `cd rustlings` to go into the generated directory.
|
const NOT_TRUSTED_MSG: &str = "It looks like you are trying to work on third-party exercises.
|
||||||
Then run `rustlings` to get started.";
|
Rustlings supports third-party exercises. But because Rustlings runs the code inside an exercise,
|
||||||
|
we need to warn you about the possibility of malicious code.
|
||||||
|
We recommend that you read all the exercise files in the `exercises` directory and check the
|
||||||
|
dependencies in the `Cargo.toml` file.
|
||||||
|
If everything looks fine and you want to trust this directory, run `rustlings trust`.
|
||||||
|
|
||||||
|
If you you are trying to work on the official exercises that were generated using `rustlings init`,
|
||||||
|
then you probably moved the directory containing them. In that case, you can run `rustlings trust`
|
||||||
|
without a problem.";
|
||||||
|
|
||||||
const FENISH_LINE: &str = "+----------------------------------------------------+
|
const FENISH_LINE: &str = "+----------------------------------------------------+
|
||||||
| You made it to the Fe-nish line! |
|
| You made it to the Fe-nish line! |
|
||||||
|
|
72
src/trust.rs
Normal file
72
src/trust.rs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
use anyhow::{Context, Error, Result};
|
||||||
|
use std::{
|
||||||
|
env,
|
||||||
|
fs::{self, OpenOptions},
|
||||||
|
io::{ErrorKind, Write},
|
||||||
|
};
|
||||||
|
|
||||||
|
const DATA_DIR_NAME: &str = "rustlings";
|
||||||
|
const TRUSTED_DIRS_FILE_NAME: &str = "trusted-dirs.txt";
|
||||||
|
|
||||||
|
pub fn trust_current_dir() -> Result<()> {
|
||||||
|
let mut path = dirs::data_dir().context("Failed to determine the data directory")?;
|
||||||
|
path.push(DATA_DIR_NAME);
|
||||||
|
if !path.is_dir() {
|
||||||
|
fs::create_dir(&path)
|
||||||
|
.with_context(|| format!("Failed to create the directory {}", path.display()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
path.push(TRUSTED_DIRS_FILE_NAME);
|
||||||
|
let mut file = OpenOptions::new()
|
||||||
|
.create(true)
|
||||||
|
.append(true)
|
||||||
|
.open(&path)
|
||||||
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to create/open the file {} in write mode",
|
||||||
|
path.display(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let dir = env::current_dir().context("Failed to get the current directory path")?;
|
||||||
|
let dir = dir.to_string_lossy();
|
||||||
|
let mut line = Vec::with_capacity(dir.as_bytes().len() + 1);
|
||||||
|
line.extend_from_slice(dir.as_bytes());
|
||||||
|
line.push(b'\n');
|
||||||
|
|
||||||
|
file.write_all(&line)
|
||||||
|
.with_context(|| format!("Failed to append to the file {}", path.display()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_dir_is_trusted() -> Result<bool> {
|
||||||
|
let mut path = dirs::data_dir().context("Failed to determine the data directory")?;
|
||||||
|
path.push(DATA_DIR_NAME);
|
||||||
|
path.push(TRUSTED_DIRS_FILE_NAME);
|
||||||
|
|
||||||
|
let content = match fs::read(&path) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => match e.kind() {
|
||||||
|
ErrorKind::NotFound => return Ok(false),
|
||||||
|
_ => {
|
||||||
|
return Err(
|
||||||
|
Error::from(e).context(format!("Failed to read the file {}", path.display()))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let current_dir = env::current_dir().context("Failed to get the current directory path")?;
|
||||||
|
let current_dir = current_dir.to_string_lossy();
|
||||||
|
|
||||||
|
for line in content.split(|c| *c == b'\n') {
|
||||||
|
if line.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if line == current_dir.as_bytes() {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
Loading…
Reference in a new issue