summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormo8it <mo8it@proton.me>2024-09-04 02:19:45 +0200
committermo8it <mo8it@proton.me>2024-09-04 02:19:45 +0200
commit247bd19f93e11fb037c945ff1dc464a1d1713471 (patch)
tree7b9b404b2c0c157f3d50bf26288a441be13aae52
parente5ed11528855f6dddc5759df3426ff1296aba87e (diff)
Canonicalize exercise paths only once
-rw-r--r--src/app_state.rs27
-rw-r--r--src/exercise.rs19
-rw-r--r--src/list/state.rs4
-rw-r--r--src/run.rs9
-rw-r--r--src/term.rs41
-rw-r--r--src/watch/state.rs6
6 files changed, 77 insertions, 29 deletions
diff --git a/src/app_state.rs b/src/app_state.rs
index 381aaf8..7123d11 100644
--- a/src/app_state.rs
+++ b/src/app_state.rs
@@ -3,7 +3,7 @@ use std::{
env,
fs::{File, OpenOptions},
io::{self, Read, Seek, StdoutLock, Write},
- path::Path,
+ path::{Path, MAIN_SEPARATOR_STR},
process::{Command, Stdio},
thread,
};
@@ -15,6 +15,7 @@ use crate::{
embedded::EMBEDDED_FILES,
exercise::{Exercise, RunnableExercise},
info_file::ExerciseInfo,
+ term,
};
const STATE_FILE_NAME: &str = ".rustlings-state.txt";
@@ -71,6 +72,7 @@ impl AppState {
format!("Failed to open or create the state file {STATE_FILE_NAME}")
})?;
+ let dir_canonical_path = term::canonicalize("exercises");
let mut exercises = exercise_infos
.into_iter()
.map(|exercise_info| {
@@ -82,10 +84,32 @@ impl AppState {
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 mut canonical_path;
+ if let Some(dir) = dir {
+ canonical_path = String::with_capacity(
+ 2 + dir_canonical_path.len() + dir.len() + name.len(),
+ );
+ canonical_path.push_str(dir_canonical_path);
+ canonical_path.push_str(MAIN_SEPARATOR_STR);
+ canonical_path.push_str(dir);
+ } else {
+ canonical_path =
+ String::with_capacity(1 + dir_canonical_path.len() + name.len());
+ canonical_path.push_str(dir_canonical_path);
+ }
+
+ canonical_path.push_str(MAIN_SEPARATOR_STR);
+ canonical_path.push_str(name);
+ canonical_path.push_str(".rs");
+ canonical_path
+ });
+
Exercise {
dir,
name,
path,
+ canonical_path,
test: exercise_info.test,
strict_clippy: exercise_info.strict_clippy,
hint,
@@ -486,6 +510,7 @@ mod tests {
dir: None,
name: "0",
path: "exercises/0.rs",
+ canonical_path: None,
test: false,
strict_clippy: false,
hint: "",
diff --git a/src/exercise.rs b/src/exercise.rs
index 11eea63..7fb2343 100644
--- a/src/exercise.rs
+++ b/src/exercise.rs
@@ -7,7 +7,7 @@ use std::io::{self, StdoutLock, Write};
use crate::{
cmd::CmdRunner,
- term::{terminal_file_link, write_ansi},
+ term::{self, terminal_file_link, write_ansi, CountedWrite},
};
/// The initial capacity of the output buffer.
@@ -18,7 +18,11 @@ pub fn solution_link_line(stdout: &mut StdoutLock, solution_path: &str) -> io::R
stdout.write_all(b"Solution")?;
stdout.queue(ResetColor)?;
stdout.write_all(b" for comparison: ")?;
- terminal_file_link(stdout, solution_path, Color::Cyan)?;
+ if let Some(canonical_path) = term::canonicalize(solution_path) {
+ terminal_file_link(stdout, solution_path, &canonical_path, Color::Cyan)?;
+ } else {
+ stdout.write_all(solution_path.as_bytes())?;
+ }
stdout.write_all(b"\n")
}
@@ -60,12 +64,23 @@ pub struct Exercise {
pub name: &'static str,
/// Path of the exercise file starting with the `exercises/` directory.
pub path: &'static str,
+ pub canonical_path: Option<String>,
pub test: bool,
pub strict_clippy: bool,
pub hint: &'static str,
pub done: bool,
}
+impl Exercise {
+ pub fn terminal_file_link<'a>(&self, writer: &mut impl CountedWrite<'a>) -> io::Result<()> {
+ if let Some(canonical_path) = self.canonical_path.as_deref() {
+ return terminal_file_link(writer, self.path, canonical_path, Color::Blue);
+ }
+
+ writer.write_str(self.path)
+ }
+}
+
pub trait RunnableExercise {
fn name(&self) -> &str;
fn dir(&self) -> Option<&str>;
diff --git a/src/list/state.rs b/src/list/state.rs
index 468049a..ed7c71f 100644
--- a/src/list/state.rs
+++ b/src/list/state.rs
@@ -13,7 +13,7 @@ use std::{
use crate::{
app_state::AppState,
exercise::Exercise,
- term::{progress_bar, terminal_file_link, CountedWrite, MaxLenWriter},
+ term::{progress_bar, CountedWrite, MaxLenWriter},
};
use super::scroll_state::ScrollState;
@@ -158,7 +158,7 @@ impl<'a> ListState<'a> {
if self.app_state.vs_code() {
writer.write_str(exercise.path)?;
} else {
- terminal_file_link(&mut writer, exercise.path, Color::Blue)?;
+ exercise.terminal_file_link(&mut writer)?;
}
next_ln(stdout)?;
diff --git a/src/run.rs b/src/run.rs
index 929b475..f0faa69 100644
--- a/src/run.rs
+++ b/src/run.rs
@@ -11,7 +11,6 @@ use std::{
use crate::{
app_state::{AppState, ExercisesProgress},
exercise::{solution_link_line, RunnableExercise, OUTPUT_CAPACITY},
- term::terminal_file_link,
};
pub fn run(app_state: &mut AppState) -> Result<()> {
@@ -26,7 +25,9 @@ pub fn run(app_state: &mut AppState) -> Result<()> {
app_state.set_pending(app_state.current_exercise_ind())?;
stdout.write_all(b"Ran ")?;
- terminal_file_link(&mut stdout, app_state.current_exercise().path, Color::Blue)?;
+ app_state
+ .current_exercise()
+ .terminal_file_link(&mut stdout)?;
stdout.write_all(b" with errors\n")?;
exit(1);
}
@@ -46,7 +47,9 @@ pub fn run(app_state: &mut AppState) -> Result<()> {
match app_state.done_current_exercise(&mut stdout)? {
ExercisesProgress::CurrentPending | ExercisesProgress::NewPending => {
stdout.write_all(b"Next exercise: ")?;
- terminal_file_link(&mut stdout, app_state.current_exercise().path, Color::Blue)?;
+ app_state
+ .current_exercise()
+ .terminal_file_link(&mut stdout)?;
stdout.write_all(b"\n")?;
}
ExercisesProgress::AllDone => (),
diff --git a/src/term.rs b/src/term.rs
index 489d658..5b557ec 100644
--- a/src/term.rs
+++ b/src/term.rs
@@ -1,14 +1,13 @@
-use std::{
- fmt, fs,
- io::{self, BufRead, StdoutLock, Write},
-};
-
use crossterm::{
cursor::MoveTo,
style::{Attribute, Color, SetAttribute, SetForegroundColor},
terminal::{Clear, ClearType},
Command, QueueableCommand,
};
+use std::{
+ fmt, fs,
+ io::{self, BufRead, StdoutLock, Write},
+};
pub struct MaxLenWriter<'a, 'b> {
pub stdout: &'a mut StdoutLock<'b>,
@@ -151,25 +150,29 @@ pub fn press_enter_prompt(stdout: &mut StdoutLock) -> io::Result<()> {
stdout.write_all(b"\n")
}
+/// Canonicalize, convert to string and remove verbatim part on Windows.
+pub fn canonicalize(path: &str) -> Option<String> {
+ fs::canonicalize(path)
+ .ok()?
+ .into_os_string()
+ .into_string()
+ .ok()
+ .map(|mut path| {
+ // Windows itself can't handle its verbatim paths.
+ if cfg!(windows) && path.as_bytes().starts_with(br"\\?\") {
+ path.drain(..4);
+ }
+
+ path
+ })
+}
+
pub fn terminal_file_link<'a>(
writer: &mut impl CountedWrite<'a>,
path: &str,
+ canonical_path: &str,
color: Color,
) -> io::Result<()> {
- let canonical_path = fs::canonicalize(path).ok();
-
- let Some(canonical_path) = canonical_path.as_deref().and_then(|p| p.to_str()) else {
- return writer.write_str(path);
- };
-
- // Windows itself can't handle its verbatim paths.
- #[cfg(windows)]
- let canonical_path = if canonical_path.len() > 5 && &canonical_path[0..4] == r"\\?\" {
- &canonical_path[4..]
- } else {
- canonical_path
- };
-
writer
.stdout()
.queue(SetForegroundColor(color))?
diff --git a/src/watch/state.rs b/src/watch/state.rs
index 1c2e2a9..fe9e274 100644
--- a/src/watch/state.rs
+++ b/src/watch/state.rs
@@ -11,7 +11,7 @@ use crate::{
app_state::{AppState, ExercisesProgress},
clear_terminal,
exercise::{solution_link_line, RunnableExercise, OUTPUT_CAPACITY},
- term::{progress_bar, terminal_file_link},
+ term::progress_bar,
};
#[derive(PartialEq, Eq)]
@@ -184,7 +184,9 @@ impl<'a> WatchState<'a> {
)?;
stdout.write_all(b"\nCurrent exercise: ")?;
- terminal_file_link(stdout, self.app_state.current_exercise().path, Color::Blue)?;
+ self.app_state
+ .current_exercise()
+ .terminal_file_link(stdout)?;
stdout.write_all(b"\n\n")?;
self.show_prompt(stdout)?;