mirror of
https://github.com/rust-lang/rustlings.git
synced 2026-03-30 19:19:19 +00:00
Borrow deserialized values
This commit is contained in:
parent
13564207cb
commit
0cbcb8964c
8
Cargo.lock
generated
8
Cargo.lock
generated
@ -640,9 +640,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.9.12+spec-1.1.0"
|
version = "1.0.3+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
|
checksum = "c7614eaf19ad818347db24addfa201729cf2a9b6fdfd9eb0ab870fcacc606c0c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
@ -655,9 +655,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.7.5+spec-1.1.0"
|
version = "1.0.0+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
|
checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -19,7 +19,7 @@ rust-version = "1.88"
|
|||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
toml = { version = "0.9", default-features = false, features = ["std", "parse", "serde"] }
|
toml = { version = "1", default-features = false, features = ["std", "parse", "serde"] }
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "rustlings"
|
name = "rustlings"
|
||||||
|
|||||||
@ -3,14 +3,15 @@ use quote::quote;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct ExerciseInfo {
|
struct ExerciseInfo<'a> {
|
||||||
name: String,
|
name: &'a str,
|
||||||
dir: String,
|
dir: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct InfoFile {
|
struct InfoFile<'a> {
|
||||||
exercises: Vec<ExerciseInfo>,
|
#[serde(borrow)]
|
||||||
|
exercises: Vec<ExerciseInfo<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
@ -37,7 +38,7 @@ pub fn include_files(_: TokenStream) -> TokenStream {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
dirs.push(exercise.dir.as_str());
|
dirs.push(exercise.dir);
|
||||||
*dir_ind = dirs.len() - 1;
|
*dir_ind = dirs.len() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -54,7 +54,7 @@ pub struct AppState {
|
|||||||
exercises: Vec<Exercise>,
|
exercises: Vec<Exercise>,
|
||||||
// Caches the number of done exercises to avoid iterating over all exercises every time.
|
// Caches the number of done exercises to avoid iterating over all exercises every time.
|
||||||
n_done: u16,
|
n_done: u16,
|
||||||
final_message: String,
|
final_message: &'static str,
|
||||||
state_file: File,
|
state_file: File,
|
||||||
// Preallocated buffer for reading and writing the state file.
|
// Preallocated buffer for reading and writing the state file.
|
||||||
file_buf: Vec<u8>,
|
file_buf: Vec<u8>,
|
||||||
@ -66,7 +66,7 @@ pub struct AppState {
|
|||||||
impl AppState {
|
impl AppState {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
exercise_infos: Vec<ExerciseInfo>,
|
exercise_infos: Vec<ExerciseInfo>,
|
||||||
final_message: String,
|
final_message: &'static str,
|
||||||
) -> Result<(Self, StateFileStatus)> {
|
) -> Result<(Self, StateFileStatus)> {
|
||||||
let cmd_runner = CmdRunner::build()?;
|
let cmd_runner = CmdRunner::build()?;
|
||||||
let mut state_file = OpenOptions::new()
|
let mut state_file = OpenOptions::new()
|
||||||
@ -87,34 +87,33 @@ impl AppState {
|
|||||||
// Leaking is not a problem because the `AppState` instance lives until
|
// Leaking is not a problem because the `AppState` instance lives until
|
||||||
// the end of the program.
|
// the end of the program.
|
||||||
let path = exercise_info.path().leak();
|
let path = exercise_info.path().leak();
|
||||||
let name = exercise_info.name.leak();
|
let hint = exercise_info.hint.trim_ascii();
|
||||||
let dir = exercise_info.dir.map(|dir| &*dir.leak());
|
|
||||||
let hint = exercise_info.hint.leak().trim_ascii();
|
|
||||||
|
|
||||||
let canonical_path = dir_canonical_path.as_deref().map(|dir_canonical_path| {
|
let canonical_path = dir_canonical_path.as_deref().map(|dir_canonical_path| {
|
||||||
let mut canonical_path;
|
let mut canonical_path;
|
||||||
if let Some(dir) = dir {
|
if let Some(dir) = exercise_info.dir {
|
||||||
canonical_path = String::with_capacity(
|
canonical_path = String::with_capacity(
|
||||||
2 + dir_canonical_path.len() + dir.len() + name.len(),
|
2 + dir_canonical_path.len() + dir.len() + exercise_info.name.len(),
|
||||||
);
|
);
|
||||||
canonical_path.push_str(dir_canonical_path);
|
canonical_path.push_str(dir_canonical_path);
|
||||||
canonical_path.push_str(MAIN_SEPARATOR_STR);
|
canonical_path.push_str(MAIN_SEPARATOR_STR);
|
||||||
canonical_path.push_str(dir);
|
canonical_path.push_str(dir);
|
||||||
} else {
|
} else {
|
||||||
canonical_path =
|
canonical_path = String::with_capacity(
|
||||||
String::with_capacity(1 + dir_canonical_path.len() + name.len());
|
1 + dir_canonical_path.len() + exercise_info.name.len(),
|
||||||
|
);
|
||||||
canonical_path.push_str(dir_canonical_path);
|
canonical_path.push_str(dir_canonical_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
canonical_path.push_str(MAIN_SEPARATOR_STR);
|
canonical_path.push_str(MAIN_SEPARATOR_STR);
|
||||||
canonical_path.push_str(name);
|
canonical_path.push_str(exercise_info.name);
|
||||||
canonical_path.push_str(".rs");
|
canonical_path.push_str(".rs");
|
||||||
canonical_path
|
canonical_path
|
||||||
});
|
});
|
||||||
|
|
||||||
Exercise {
|
Exercise {
|
||||||
dir,
|
dir: exercise_info.dir,
|
||||||
name,
|
name: exercise_info.name,
|
||||||
path,
|
path,
|
||||||
canonical_path,
|
canonical_path,
|
||||||
test: exercise_info.test,
|
test: exercise_info.test,
|
||||||
@ -616,7 +615,7 @@ mod tests {
|
|||||||
current_exercise_ind: 0,
|
current_exercise_ind: 0,
|
||||||
exercises: vec![dummy_exercise(), dummy_exercise(), dummy_exercise()],
|
exercises: vec![dummy_exercise(), dummy_exercise(), dummy_exercise()],
|
||||||
n_done: 0,
|
n_done: 0,
|
||||||
final_message: String::new(),
|
final_message: "",
|
||||||
state_file: tempfile::tempfile().unwrap(),
|
state_file: tempfile::tempfile().unwrap(),
|
||||||
file_buf: Vec::new(),
|
file_buf: Vec::new(),
|
||||||
official_exercises: true,
|
official_exercises: true,
|
||||||
|
|||||||
@ -38,7 +38,7 @@ pub fn append_bins(
|
|||||||
buf.extend_from_slice(b"\", path = \"");
|
buf.extend_from_slice(b"\", path = \"");
|
||||||
buf.extend_from_slice(exercise_path_prefix);
|
buf.extend_from_slice(exercise_path_prefix);
|
||||||
buf.extend_from_slice(b"exercises/");
|
buf.extend_from_slice(b"exercises/");
|
||||||
if let Some(dir) = &exercise_info.dir {
|
if let Some(dir) = exercise_info.dir {
|
||||||
buf.extend_from_slice(dir.as_bytes());
|
buf.extend_from_slice(dir.as_bytes());
|
||||||
buf.push(b'/');
|
buf.push(b'/');
|
||||||
}
|
}
|
||||||
@ -56,7 +56,7 @@ pub fn append_bins(
|
|||||||
buf.extend_from_slice(b"\", path = \"");
|
buf.extend_from_slice(b"\", path = \"");
|
||||||
buf.extend_from_slice(exercise_path_prefix);
|
buf.extend_from_slice(exercise_path_prefix);
|
||||||
buf.extend_from_slice(b"solutions/");
|
buf.extend_from_slice(b"solutions/");
|
||||||
if let Some(dir) = &exercise_info.dir {
|
if let Some(dir) = exercise_info.dir {
|
||||||
buf.extend_from_slice(dir.as_bytes());
|
buf.extend_from_slice(dir.as_bytes());
|
||||||
buf.push(b'/');
|
buf.push(b'/');
|
||||||
}
|
}
|
||||||
@ -106,19 +106,19 @@ mod tests {
|
|||||||
fn test_bins() {
|
fn test_bins() {
|
||||||
let exercise_infos = [
|
let exercise_infos = [
|
||||||
ExerciseInfo {
|
ExerciseInfo {
|
||||||
name: String::from("1"),
|
name: "1",
|
||||||
dir: None,
|
dir: None,
|
||||||
test: true,
|
test: true,
|
||||||
strict_clippy: true,
|
strict_clippy: true,
|
||||||
hint: String::new(),
|
hint: "",
|
||||||
skip_check_unsolved: false,
|
skip_check_unsolved: false,
|
||||||
},
|
},
|
||||||
ExerciseInfo {
|
ExerciseInfo {
|
||||||
name: String::from("2"),
|
name: "2",
|
||||||
dir: Some(String::from("d")),
|
dir: Some("d"),
|
||||||
test: false,
|
test: false,
|
||||||
strict_clippy: false,
|
strict_clippy: false,
|
||||||
hint: String::new(),
|
hint: "",
|
||||||
skip_check_unsolved: false,
|
skip_check_unsolved: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -63,7 +63,7 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
|
|||||||
|
|
||||||
let mut file_buf = String::with_capacity(1 << 14);
|
let mut file_buf = String::with_capacity(1 << 14);
|
||||||
for exercise_info in &info_file.exercises {
|
for exercise_info in &info_file.exercises {
|
||||||
let name = exercise_info.name.as_str();
|
let name = exercise_info.name;
|
||||||
if name.is_empty() {
|
if name.is_empty() {
|
||||||
bail!("Found an empty exercise name in `info.toml`");
|
bail!("Found an empty exercise name in `info.toml`");
|
||||||
}
|
}
|
||||||
@ -76,7 +76,7 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
|
|||||||
bail!("Char `{c}` in the exercise name `{name}` is not allowed");
|
bail!("Char `{c}` in the exercise name `{name}` is not allowed");
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(dir) = &exercise_info.dir {
|
if let Some(dir) = exercise_info.dir {
|
||||||
if dir.is_empty() {
|
if dir.is_empty() {
|
||||||
bail!("The exercise `{name}` has an empty dir name in `info.toml`");
|
bail!("The exercise `{name}` has an empty dir name in `info.toml`");
|
||||||
}
|
}
|
||||||
@ -214,7 +214,7 @@ fn check_exercises_unsolved(
|
|||||||
Some(
|
Some(
|
||||||
thread::Builder::new()
|
thread::Builder::new()
|
||||||
.spawn(|| exercise_info.run_exercise(None, cmd_runner))
|
.spawn(|| exercise_info.run_exercise(None, cmd_runner))
|
||||||
.map(|handle| (exercise_info.name.as_str(), handle)),
|
.map(|handle| (exercise_info.name, handle)),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, _>>()
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
|||||||
@ -85,7 +85,7 @@ impl EmbeddedFiles {
|
|||||||
exercise_path.truncate(prefix.len());
|
exercise_path.truncate(prefix.len());
|
||||||
exercise_path.push_str(dir.name);
|
exercise_path.push_str(dir.name);
|
||||||
exercise_path.push('/');
|
exercise_path.push('/');
|
||||||
exercise_path.push_str(&exercise_info.name);
|
exercise_path.push_str(exercise_info.name);
|
||||||
exercise_path.push_str(".rs");
|
exercise_path.push_str(".rs");
|
||||||
|
|
||||||
fs::write(&exercise_path, exercise_files.exercise)
|
fs::write(&exercise_path, exercise_files.exercise)
|
||||||
@ -141,13 +141,14 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct ExerciseInfo {
|
struct ExerciseInfo<'a> {
|
||||||
dir: String,
|
dir: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct InfoFile {
|
struct InfoFile<'a> {
|
||||||
exercises: Vec<ExerciseInfo>,
|
#[serde(borrow)]
|
||||||
|
exercises: Vec<ExerciseInfo<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -8,9 +8,9 @@ use crate::{embedded::EMBEDDED_FILES, exercise::RunnableExercise};
|
|||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct ExerciseInfo {
|
pub struct ExerciseInfo {
|
||||||
/// Exercise's unique name.
|
/// Exercise's unique name.
|
||||||
pub name: String,
|
pub name: &'static str,
|
||||||
/// Exercise's directory name inside the `exercises/` directory.
|
/// Exercise's directory name inside the `exercises/` directory.
|
||||||
pub dir: Option<String>,
|
pub dir: Option<&'static str>,
|
||||||
/// Run `cargo test` on the exercise.
|
/// Run `cargo test` on the exercise.
|
||||||
#[serde(default = "default_true")]
|
#[serde(default = "default_true")]
|
||||||
pub test: bool,
|
pub test: bool,
|
||||||
@ -18,7 +18,7 @@ pub struct ExerciseInfo {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub strict_clippy: bool,
|
pub strict_clippy: bool,
|
||||||
/// The exercise's hint to be shown to the user on request.
|
/// The exercise's hint to be shown to the user on request.
|
||||||
pub hint: String,
|
pub hint: &'static str,
|
||||||
/// The exercise is already solved. Ignore it when checking that all exercises are unsolved.
|
/// The exercise is already solved. Ignore it when checking that all exercises are unsolved.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub skip_check_unsolved: bool,
|
pub skip_check_unsolved: bool,
|
||||||
@ -31,7 +31,7 @@ const fn default_true() -> bool {
|
|||||||
impl ExerciseInfo {
|
impl ExerciseInfo {
|
||||||
/// Path to the exercise file starting with the `exercises/` directory.
|
/// Path to the exercise file starting with the `exercises/` directory.
|
||||||
pub fn path(&self) -> String {
|
pub fn path(&self) -> String {
|
||||||
let mut path = if let Some(dir) = &self.dir {
|
let mut path = if let Some(dir) = self.dir {
|
||||||
// 14 = 10 + 1 + 3
|
// 14 = 10 + 1 + 3
|
||||||
// exercises/ + / + .rs
|
// exercises/ + / + .rs
|
||||||
let mut path = String::with_capacity(14 + dir.len() + self.name.len());
|
let mut path = String::with_capacity(14 + dir.len() + self.name.len());
|
||||||
@ -47,7 +47,7 @@ impl ExerciseInfo {
|
|||||||
path
|
path
|
||||||
};
|
};
|
||||||
|
|
||||||
path.push_str(&self.name);
|
path.push_str(self.name);
|
||||||
path.push_str(".rs");
|
path.push_str(".rs");
|
||||||
|
|
||||||
path
|
path
|
||||||
@ -57,12 +57,12 @@ impl ExerciseInfo {
|
|||||||
impl RunnableExercise for ExerciseInfo {
|
impl RunnableExercise for ExerciseInfo {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
&self.name
|
self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn dir(&self) -> Option<&str> {
|
fn dir(&self) -> Option<&str> {
|
||||||
self.dir.as_deref()
|
self.dir
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -82,9 +82,9 @@ pub struct InfoFile {
|
|||||||
/// For possible breaking changes in the future for community exercises.
|
/// For possible breaking changes in the future for community exercises.
|
||||||
pub format_version: u8,
|
pub format_version: u8,
|
||||||
/// Shown to users when starting with the exercises.
|
/// Shown to users when starting with the exercises.
|
||||||
pub welcome_message: Option<String>,
|
pub welcome_message: Option<&'static str>,
|
||||||
/// Shown to users after finishing all exercises.
|
/// Shown to users after finishing all exercises.
|
||||||
pub final_message: Option<String>,
|
pub final_message: Option<&'static str>,
|
||||||
/// List of all exercises.
|
/// List of all exercises.
|
||||||
pub exercises: Vec<ExerciseInfo>,
|
pub exercises: Vec<ExerciseInfo>,
|
||||||
}
|
}
|
||||||
@ -95,7 +95,8 @@ impl InfoFile {
|
|||||||
pub fn parse() -> Result<Self> {
|
pub fn parse() -> Result<Self> {
|
||||||
// Read a local `info.toml` if it exists.
|
// Read a local `info.toml` if it exists.
|
||||||
let slf = match fs::read_to_string("info.toml") {
|
let slf = match fs::read_to_string("info.toml") {
|
||||||
Ok(file_content) => toml::de::from_str::<Self>(&file_content)
|
// Leaking is fine since `InfoFile` is used until the end of the program.
|
||||||
|
Ok(file_content) => toml::de::from_str::<Self>(file_content.leak())
|
||||||
.context("Failed to parse the `info.toml` file")?,
|
.context("Failed to parse the `info.toml` file")?,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if e.kind() == ErrorKind::NotFound {
|
if e.kind() == ErrorKind::NotFound {
|
||||||
|
|||||||
@ -8,7 +8,7 @@ use std::{
|
|||||||
env::set_current_dir,
|
env::set_current_dir,
|
||||||
fs::{self, create_dir},
|
fs::{self, create_dir},
|
||||||
io::{self, Write},
|
io::{self, Write},
|
||||||
path::{Path, PathBuf},
|
path::Path,
|
||||||
process::{Command, Stdio},
|
process::{Command, Stdio},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -18,8 +18,9 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct CargoLocateProject {
|
struct CargoLocateProject<'a> {
|
||||||
root: PathBuf,
|
#[serde(borrow)]
|
||||||
|
root: &'a Path,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init() -> Result<()> {
|
pub fn init() -> Result<()> {
|
||||||
@ -72,7 +73,7 @@ pub fn init() -> Result<()> {
|
|||||||
)?
|
)?
|
||||||
.root;
|
.root;
|
||||||
|
|
||||||
let workspace_manifest_content = fs::read_to_string(&workspace_manifest)
|
let workspace_manifest_content = fs::read_to_string(workspace_manifest)
|
||||||
.with_context(|| format!("Failed to read the file {}", workspace_manifest.display()))?;
|
.with_context(|| format!("Failed to read the file {}", workspace_manifest.display()))?;
|
||||||
if !workspace_manifest_content.contains("[workspace]")
|
if !workspace_manifest_content.contains("[workspace]")
|
||||||
&& !workspace_manifest_content.contains("workspace.")
|
&& !workspace_manifest_content.contains("workspace.")
|
||||||
|
|||||||
@ -128,7 +128,7 @@ fn main() -> Result<ExitCode> {
|
|||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
// For the notify event handler thread.
|
// For the notify event handler thread.
|
||||||
// Leaking is not a problem because the slice lives until the end of the program.
|
// Leaking is fine since the slice is used until the end of the program.
|
||||||
Some(
|
Some(
|
||||||
&*app_state
|
&*app_state
|
||||||
.exercises()
|
.exercises()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user