Upgrade to edition 2024

This commit is contained in:
mo8it 2025-02-18 20:10:52 +01:00
parent 298be671b9
commit d9872f2615
20 changed files with 120 additions and 79 deletions

View file

@ -1,3 +1,10 @@
## Unreleased
### Changed
- Upgrade to Rust edition 2024
- Raise the minimum supported Rust version to `1.85`
<a name="6.4.0"></a>
## 6.4.0 (2024-11-11)

View file

@ -15,8 +15,8 @@ authors = [
]
repository = "https://github.com/rust-lang/rustlings"
license = "MIT"
edition = "2021" # On Update: Update the edition of the `rustfmt` command that checks the solutions.
rust-version = "1.80"
edition = "2024" # On Update: Update the edition of the `rustfmt` command that checks the solutions.
rust-version = "1.85"
[workspace.dependencies]
serde = { version = "1.0.217", features = ["derive"] }

View file

@ -192,7 +192,7 @@ bin = [
[package]
name = "exercises"
edition = "2021"
edition = "2024"
# Don't publish the exercises on crates.io!
publish = false

View file

@ -1,11 +1,11 @@
use anyhow::{bail, Context, Error, Result};
use crossterm::{cursor, terminal, QueueableCommand};
use anyhow::{Context, Error, Result, bail};
use crossterm::{QueueableCommand, cursor, terminal};
use std::{
collections::HashSet,
env,
fs::{File, OpenOptions},
io::{Read, Seek, StdoutLock, Write},
path::{Path, MAIN_SEPARATOR_STR},
path::{MAIN_SEPARATOR_STR, Path},
process::{Command, Stdio},
sync::{
atomic::{AtomicUsize, Ordering::Relaxed},
@ -427,32 +427,34 @@ impl AppState {
let next_exercise_ind = &next_exercise_ind;
let slf = &self;
thread::Builder::new()
.spawn_scoped(s, move || loop {
let exercise_ind = next_exercise_ind.fetch_add(1, Relaxed);
let Some(exercise) = slf.exercises.get(exercise_ind) else {
// No more exercises.
break;
};
.spawn_scoped(s, move || {
loop {
let exercise_ind = next_exercise_ind.fetch_add(1, Relaxed);
let Some(exercise) = slf.exercises.get(exercise_ind) else {
// No more exercises.
break;
};
if exercise_progress_sender
.send((exercise_ind, CheckProgress::Checking))
.is_err()
{
break;
};
if exercise_progress_sender
.send((exercise_ind, CheckProgress::Checking))
.is_err()
{
break;
};
let success = exercise.run_exercise(None, &slf.cmd_runner);
let progress = match success {
Ok(true) => CheckProgress::Done,
Ok(false) => CheckProgress::Pending,
Err(_) => CheckProgress::None,
};
let success = exercise.run_exercise(None, &slf.cmd_runner);
let progress = match success {
Ok(true) => CheckProgress::Done,
Ok(false) => CheckProgress::Pending,
Err(_) => CheckProgress::None,
};
if exercise_progress_sender
.send((exercise_ind, progress))
.is_err()
{
break;
if exercise_progress_sender
.send((exercise_ind, progress))
.is_err()
{
break;
}
}
})
.context("Failed to spawn a thread to check all exercises")?;

View file

@ -1,4 +1,4 @@
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
use serde::Deserialize;
use std::{
io::Read,

View file

@ -1,4 +1,4 @@
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
use clap::Subcommand;
use std::path::PathBuf;

View file

@ -1,8 +1,8 @@
use anyhow::{anyhow, bail, Context, Error, Result};
use anyhow::{Context, Error, Result, anyhow, bail};
use std::{
cmp::Ordering,
collections::HashSet,
fs::{self, read_dir, OpenOptions},
fs::{self, OpenOptions, read_dir},
io::{self, Read, Write},
path::{Path, PathBuf},
process::{Command, Stdio},
@ -10,11 +10,11 @@ use std::{
};
use crate::{
cargo_toml::{append_bins, bins_start_end_ind, BINS_BUFFER_CAPACITY},
cmd::CmdRunner,
exercise::{RunnableExercise, OUTPUT_CAPACITY},
info_file::{ExerciseInfo, InfoFile},
CURRENT_FORMAT_VERSION,
cargo_toml::{BINS_BUFFER_CAPACITY, append_bins, bins_start_end_ind},
cmd::CmdRunner,
exercise::{OUTPUT_CAPACITY, RunnableExercise},
info_file::{ExerciseInfo, InfoFile},
};
const MAX_N_EXERCISES: usize = 999;
@ -42,10 +42,14 @@ fn check_cargo_toml(
if old_bins != new_bins {
if cfg!(debug_assertions) {
bail!("The file `dev/Cargo.toml` is outdated. Run `cargo run -- dev update` to update it. Then run `cargo run -- dev check` again");
bail!(
"The file `dev/Cargo.toml` is outdated. Run `cargo run -- dev update` to update it. Then run `cargo run -- dev check` again"
);
}
bail!("The file `Cargo.toml` is outdated. Run `rustlings dev update` to update it. Then run `rustlings dev check` again");
bail!(
"The file `Cargo.toml` is outdated. Run `rustlings dev update` to update it. Then run `rustlings dev check` again"
);
}
Ok(())
@ -63,7 +67,9 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
bail!("Found an empty exercise name in `info.toml`");
}
if name.len() > MAX_EXERCISE_NAME_LEN {
bail!("The length of the exercise name `{name}` is bigger than the maximum {MAX_EXERCISE_NAME_LEN}");
bail!(
"The length of the exercise name `{name}` is bigger than the maximum {MAX_EXERCISE_NAME_LEN}"
);
}
if let Some(c) = forbidden_char(name) {
bail!("Char `{c}` in the exercise name `{name}` is not allowed");
@ -79,7 +85,9 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
}
if exercise_info.hint.trim_ascii().is_empty() {
bail!("The exercise `{name}` has an empty hint. Please provide a hint or at least tell the user why a hint isn't needed for this exercise");
bail!(
"The exercise `{name}` has an empty hint. Please provide a hint or at least tell the user why a hint isn't needed for this exercise"
);
}
if !names.insert(name) {
@ -96,20 +104,28 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
.with_context(|| format!("Failed to read the file {path}"))?;
if !file_buf.contains("fn main()") {
bail!("The `main` function is missing in the file `{path}`.\nCreate at least an empty `main` function to avoid language server errors");
bail!(
"The `main` function is missing in the file `{path}`.\nCreate at least an empty `main` function to avoid language server errors"
);
}
if !file_buf.contains("// TODO") {
bail!("Didn't find any `// TODO` comment in the file `{path}`.\nYou need to have at least one such comment to guide the user.");
bail!(
"Didn't find any `// TODO` comment in the file `{path}`.\nYou need to have at least one such comment to guide the user."
);
}
let contains_tests = file_buf.contains("#[test]\n");
if exercise_info.test {
if !contains_tests {
bail!("The file `{path}` doesn't contain any tests. If you don't want to add tests to this exercise, set `test = false` for this exercise in the `info.toml` file");
bail!(
"The file `{path}` doesn't contain any tests. If you don't want to add tests to this exercise, set `test = false` for this exercise in the `info.toml` file"
);
}
} else if contains_tests {
bail!("The file `{path}` contains tests annotated with `#[test]` but the exercise `{name}` has `test = false` in the `info.toml` file");
bail!(
"The file `{path}` contains tests annotated with `#[test]` but the exercise `{name}` has `test = false` in the `info.toml` file"
);
}
file_buf.clear();
@ -125,7 +141,10 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
// Only one level of directory nesting is allowed.
fn check_unexpected_files(dir: &str, allowed_rust_files: &HashSet<PathBuf>) -> Result<()> {
let unexpected_file = |path: &Path| {
anyhow!("Found the file `{}`. Only `README.md` and Rust files related to an exercise in `info.toml` are allowed in the `{dir}` directory", path.display())
anyhow!(
"Found the file `{}`. Only `README.md` and Rust files related to an exercise in `info.toml` are allowed in the `{dir}` directory",
path.display()
)
};
for entry in read_dir(dir).with_context(|| format!("Failed to open the `{dir}` directory"))? {
@ -154,7 +173,10 @@ fn check_unexpected_files(dir: &str, allowed_rust_files: &HashSet<PathBuf>) -> R
let path = entry.path();
if !entry.file_type().unwrap().is_file() {
bail!("Found `{}` but expected only files. Only one level of exercise nesting is allowed", path.display());
bail!(
"Found `{}` but expected only files. Only one level of exercise nesting is allowed",
path.display()
);
}
let file_name = path.file_name().unwrap();
@ -224,8 +246,12 @@ fn check_exercises_unsolved(
fn check_exercises(info_file: &'static InfoFile, cmd_runner: &'static CmdRunner) -> Result<()> {
match info_file.format_version.cmp(&CURRENT_FORMAT_VERSION) {
Ordering::Less => bail!("`format_version` < {CURRENT_FORMAT_VERSION} (supported version)\nPlease migrate to the latest format version"),
Ordering::Greater => bail!("`format_version` > {CURRENT_FORMAT_VERSION} (supported version)\nTry updating the Rustlings program"),
Ordering::Less => bail!(
"`format_version` < {CURRENT_FORMAT_VERSION} (supported version)\nPlease migrate to the latest format version"
),
Ordering::Greater => bail!(
"`format_version` > {CURRENT_FORMAT_VERSION} (supported version)\nTry updating the Rustlings program"
),
Ordering::Equal => (),
}

View file

@ -1,4 +1,4 @@
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
use std::{
env::set_current_dir,
fs::{self, create_dir},
@ -6,7 +6,7 @@ use std::{
process::Command,
};
use crate::{init::RUST_ANALYZER_TOML, CURRENT_FORMAT_VERSION};
use crate::{CURRENT_FORMAT_VERSION, init::RUST_ANALYZER_TOML};
// Create a directory relative to the current directory and print its path.
fn create_rel_dir(dir_name: &str, current_dir: &str) -> Result<()> {
@ -55,7 +55,9 @@ pub fn new(path: &Path, no_git: bool) -> Result<()> {
write_rel_file(
"info.toml",
&dir_path_str,
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}"
),
)?;
write_rel_file("Cargo.toml", &dir_path_str, CARGO_TOML)?;
@ -130,7 +132,7 @@ bin = []
[package]
name = "exercises"
edition = "2021"
edition = "2024"
# Don't publish the exercises on crates.io!
publish = false

View file

@ -1,13 +1,13 @@
use anyhow::Result;
use crossterm::{
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
QueueableCommand,
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
};
use std::io::{self, StdoutLock, Write};
use crate::{
cmd::CmdRunner,
term::{self, terminal_file_link, write_ansi, CountedWrite},
term::{self, CountedWrite, terminal_file_link, write_ansi},
};
/// The initial capacity of the output buffer.

View file

@ -1,4 +1,4 @@
use anyhow::{bail, Context, Error, Result};
use anyhow::{Context, Error, Result, bail};
use serde::Deserialize;
use std::{fs, io::ErrorKind};

View file

@ -1,7 +1,7 @@
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
use crossterm::{
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
QueueableCommand,
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
};
use serde::Deserialize;
use std::{
@ -57,7 +57,9 @@ pub fn init() -> Result<()> {
if !workspace_manifest_content.contains("[workspace]\n")
&& !workspace_manifest_content.contains("workspace.")
{
bail!("The current directory is already part of a Cargo project.\nPlease initialize Rustlings in a different directory");
bail!(
"The current directory is already part of a Cargo project.\nPlease initialize Rustlings in a different directory"
);
}
stdout.write_all(b"This command will create the directory `rustlings/` as a member of this Cargo workspace.\nPress ENTER to continue ")?;
@ -75,7 +77,9 @@ pub fn init() -> Result<()> {
.stdout(Stdio::null())
.status()?;
if !status.success() {
bail!("Failed to initialize a new Cargo workspace member.\nPlease initialize Rustlings in a different directory");
bail!(
"Failed to initialize a new Cargo workspace member.\nPlease initialize Rustlings in a different directory"
);
}
stdout.write_all(b"The directory `rustlings` has been added to `workspace.members` in the `Cargo.toml` file of this Cargo workspace.\n")?;

View file

@ -1,14 +1,13 @@
use anyhow::{Context, Result};
use crossterm::{
cursor,
QueueableCommand, cursor,
event::{
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, MouseEventKind,
},
terminal::{
disable_raw_mode, enable_raw_mode, DisableLineWrap, EnableLineWrap, EnterAlternateScreen,
LeaveAlternateScreen,
DisableLineWrap, EnableLineWrap, EnterAlternateScreen, LeaveAlternateScreen,
disable_raw_mode, enable_raw_mode,
},
QueueableCommand,
};
use std::io::{self, StdoutLock, Write};

View file

@ -1,11 +1,11 @@
use anyhow::{Context, Result};
use crossterm::{
QueueableCommand,
cursor::{MoveTo, MoveToNextLine},
style::{
Attribute, Attributes, Color, ResetColor, SetAttribute, SetAttributes, SetForegroundColor,
},
terminal::{self, BeginSynchronizedUpdate, Clear, ClearType, EndSynchronizedUpdate},
QueueableCommand,
};
use std::{
fmt::Write as _,
@ -15,7 +15,7 @@ use std::{
use crate::{
app_state::AppState,
exercise::Exercise,
term::{progress_bar, CountedWrite, MaxLenWriter},
term::{CountedWrite, MaxLenWriter, progress_bar},
};
use super::scroll_state::ScrollState;

View file

@ -1,4 +1,4 @@
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
use app_state::StateFileStatus;
use clap::{Parser, Subcommand};
use std::{

View file

@ -1,7 +1,7 @@
use anyhow::Result;
use crossterm::{
style::{Color, ResetColor, SetForegroundColor},
QueueableCommand,
style::{Color, ResetColor, SetForegroundColor},
};
use std::{
io::{self, Write},
@ -10,7 +10,7 @@ use std::{
use crate::{
app_state::{AppState, ExercisesProgress},
exercise::{solution_link_line, RunnableExercise, OUTPUT_CAPACITY},
exercise::{OUTPUT_CAPACITY, RunnableExercise, solution_link_line},
};
pub fn run(app_state: &mut AppState) -> Result<ExitCode> {

View file

@ -1,8 +1,8 @@
use crossterm::{
Command, QueueableCommand,
cursor::MoveTo,
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
terminal::{Clear, ClearType},
Command, QueueableCommand,
};
use std::{
fmt, fs,

View file

@ -1,18 +1,18 @@
use anyhow::{Context, Result};
use notify::{
event::{AccessKind, AccessMode, MetadataKind, ModifyKind, RenameMode},
Event, EventKind,
event::{AccessKind, AccessMode, MetadataKind, ModifyKind, RenameMode},
};
use std::{
sync::{
atomic::Ordering::Relaxed,
mpsc::{sync_channel, RecvTimeoutError, Sender, SyncSender},
mpsc::{RecvTimeoutError, Sender, SyncSender, sync_channel},
},
thread,
time::Duration,
};
use super::{WatchEvent, EXERCISE_RUNNING};
use super::{EXERCISE_RUNNING, WatchEvent};
const DEBOUNCE_DURATION: Duration = Duration::from_millis(200);

View file

@ -1,24 +1,25 @@
use anyhow::{Context, Result};
use crossterm::{
QueueableCommand,
style::{
Attribute, Attributes, Color, ResetColor, SetAttribute, SetAttributes, SetForegroundColor,
},
terminal, QueueableCommand,
terminal,
};
use std::{
io::{self, Read, StdoutLock, Write},
sync::mpsc::{sync_channel, Sender, SyncSender},
sync::mpsc::{Sender, SyncSender, sync_channel},
thread,
};
use crate::{
app_state::{AppState, ExercisesProgress},
clear_terminal,
exercise::{solution_link_line, RunnableExercise, OUTPUT_CAPACITY},
exercise::{OUTPUT_CAPACITY, RunnableExercise, solution_link_line},
term::progress_bar,
};
use super::{terminal_event::terminal_event_handler, InputPauseGuard, WatchEvent};
use super::{InputPauseGuard, WatchEvent, terminal_event::terminal_event_handler};
const HEADING_ATTRIBUTES: Attributes = Attributes::none()
.with(Attribute::Bold)

View file

@ -4,7 +4,7 @@ use std::sync::{
mpsc::{Receiver, Sender},
};
use super::{WatchEvent, EXERCISE_RUNNING};
use super::{EXERCISE_RUNNING, WatchEvent};
pub enum InputEvent {
Next,

View file

@ -7,5 +7,5 @@ bin = [
[package]
name = "test_exercises"
edition = "2021"
edition = "2024"
publish = false