Dump solution and show its path

This commit is contained in:
mo8it 2024-04-24 02:52:30 +02:00
parent edf5762612
commit 8a085a0a85
7 changed files with 104 additions and 52 deletions

View file

@ -257,6 +257,41 @@ impl AppState {
} }
} }
pub fn current_solution_path(&self) -> Result<Option<String>> {
if DEBUG_PROFILE {
return Ok(None);
}
let current_exercise = self.current_exercise();
if self.official_exercises {
let dir_name = current_exercise
.dir
.context("Official exercises must be nested in the `exercises` directory")?;
let solution_path = format!("solutions/{dir_name}/{}.rs", current_exercise.name);
EMBEDDED_FILES.write_solution_to_disk(
self.current_exercise_ind,
dir_name,
&solution_path,
)?;
Ok(Some(solution_path))
} else {
let solution_path = if let Some(dir) = current_exercise.dir {
format!("solutions/{dir}/{}.rs", current_exercise.name)
} else {
format!("solutions/{}.rs", current_exercise.name)
};
if Path::new(&solution_path).exists() {
return Ok(Some(solution_path));
}
Ok(None)
}
}
pub fn done_current_exercise(&mut self, writer: &mut StdoutLock) -> Result<ExercisesProgress> { pub fn done_current_exercise(&mut self, writer: &mut StdoutLock) -> Result<ExercisesProgress> {
let exercise = &mut self.exercises[self.current_exercise_ind]; let exercise = &mut self.exercises[self.current_exercise_ind];
if !exercise.done { if !exercise.done {
@ -264,16 +299,6 @@ impl AppState {
self.n_done += 1; self.n_done += 1;
} }
if self.official_exercises && !DEBUG_PROFILE {
EMBEDDED_FILES.write_solution_to_disk(
self.current_exercise_ind,
exercise
.dir
.context("Official exercises must be nested in the `exercises` directory")?,
exercise.name,
)?;
}
let Some(ind) = self.next_pending_exercise_ind() else { let Some(ind) = self.next_pending_exercise_ind() else {
writer.write_all(RERUNNING_ALL_EXERCISES_MSG)?; writer.write_all(RERUNNING_ALL_EXERCISES_MSG)?;

View file

@ -113,14 +113,12 @@ impl EmbeddedFiles {
&self, &self,
exercise_ind: usize, exercise_ind: usize,
dir_name: &str, dir_name: &str,
exercise_name: &str, path: &str,
) -> Result<()> { ) -> Result<()> {
let dir_path = format!("solutions/{dir_name}"); let dir_path = format!("solutions/{dir_name}");
create_dir_all(&dir_path).context("Failed to create the directory {dir_path}")?; create_dir_all(&dir_path)
.with_context(|| format!("Failed to create the directory {dir_path}"))?;
WriteStrategy::Overwrite.write( WriteStrategy::Overwrite.write(path, self.exercise_files[exercise_ind].solution)
&format!("{dir_path}/{exercise_name}.rs"),
self.exercise_files[exercise_ind].solution,
)
} }
} }

View file

@ -2,33 +2,11 @@ use anyhow::{Context, Result};
use crossterm::style::{style, StyledContent, Stylize}; use crossterm::style::{style, StyledContent, Stylize};
use std::{ use std::{
fmt::{self, Display, Formatter}, fmt::{self, Display, Formatter},
fs,
path::Path, path::Path,
process::{Command, Output}, process::{Command, Output},
}; };
use crate::{info_file::Mode, DEBUG_PROFILE}; use crate::{info_file::Mode, terminal_link::TerminalFileLink, DEBUG_PROFILE};
pub struct TerminalFileLink<'a> {
path: &'a str,
}
impl<'a> Display for TerminalFileLink<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if let Ok(Some(canonical_path)) = fs::canonicalize(self.path)
.as_deref()
.map(|path| path.to_str())
{
write!(
f,
"\x1b]8;;file://{}\x1b\\{}\x1b]8;;\x1b\\",
canonical_path, self.path,
)
} else {
write!(f, "{}", self.path,)
}
}
}
pub struct Exercise { pub struct Exercise {
pub dir: Option<&'static str>, pub dir: Option<&'static str>,
@ -85,9 +63,7 @@ impl Exercise {
} }
pub fn terminal_link(&self) -> StyledContent<TerminalFileLink<'_>> { pub fn terminal_link(&self) -> StyledContent<TerminalFileLink<'_>> {
style(TerminalFileLink { path: self.path }) style(TerminalFileLink(self.path)).underlined().blue()
.underlined()
.blue()
} }
} }

View file

@ -23,6 +23,7 @@ mod init;
mod list; mod list;
mod progress_bar; mod progress_bar;
mod run; mod run;
mod terminal_link;
mod watch; mod watch;
const CURRENT_FORMAT_VERSION: u8 = 1; const CURRENT_FORMAT_VERSION: u8 = 1;

View file

@ -1,8 +1,11 @@
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use crossterm::style::Stylize; use crossterm::style::{style, Stylize};
use std::io::{self, Write}; use std::io::{self, Write};
use crate::app_state::{AppState, ExercisesProgress}; use crate::{
app_state::{AppState, ExercisesProgress},
terminal_link::TerminalFileLink,
};
pub fn run(app_state: &mut AppState) -> Result<()> { pub fn run(app_state: &mut AppState) -> Result<()> {
let exercise = app_state.current_exercise(); let exercise = app_state.current_exercise();
@ -29,6 +32,13 @@ pub fn run(app_state: &mut AppState) -> Result<()> {
exercise.path.green(), exercise.path.green(),
))?; ))?;
if let Some(solution_path) = app_state.current_solution_path()? {
println!(
"\nA solution file can be found at {}\n",
style(TerminalFileLink(&solution_path)).underlined().green(),
);
}
match app_state.done_current_exercise(&mut stdout)? { match app_state.done_current_exercise(&mut stdout)? {
ExercisesProgress::AllDone => (), ExercisesProgress::AllDone => (),
ExercisesProgress::Pending => println!( ExercisesProgress::Pending => println!(

23
src/terminal_link.rs Normal file
View file

@ -0,0 +1,23 @@
use std::{
fmt::{self, Display, Formatter},
fs,
};
pub struct TerminalFileLink<'a>(pub &'a str);
impl<'a> Display for TerminalFileLink<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if let Ok(Some(canonical_path)) = fs::canonicalize(self.0)
.as_deref()
.map(|path| path.to_str())
{
write!(
f,
"\x1b]8;;file://{}\x1b\\{}\x1b]8;;\x1b\\",
canonical_path, self.0,
)
} else {
write!(f, "{}", self.0)
}
}
}

View file

@ -1,6 +1,6 @@
use anyhow::Result; use anyhow::Result;
use crossterm::{ use crossterm::{
style::Stylize, style::{style, Stylize},
terminal::{size, Clear, ClearType}, terminal::{size, Clear, ClearType},
ExecutableCommand, ExecutableCommand,
}; };
@ -9,15 +9,22 @@ use std::io::{self, StdoutLock, Write};
use crate::{ use crate::{
app_state::{AppState, ExercisesProgress}, app_state::{AppState, ExercisesProgress},
progress_bar::progress_bar, progress_bar::progress_bar,
terminal_link::TerminalFileLink,
}; };
enum DoneStatus {
DoneWithSolution(String),
DoneWithoutSolution,
Pending,
}
pub struct WatchState<'a> { pub struct WatchState<'a> {
writer: StdoutLock<'a>, writer: StdoutLock<'a>,
app_state: &'a mut AppState, app_state: &'a mut AppState,
stdout: Option<Vec<u8>>, stdout: Option<Vec<u8>>,
stderr: Option<Vec<u8>>, stderr: Option<Vec<u8>>,
show_hint: bool, show_hint: bool,
show_done: bool, done_status: DoneStatus,
manual_run: bool, manual_run: bool,
} }
@ -31,7 +38,7 @@ impl<'a> WatchState<'a> {
stdout: None, stdout: None,
stderr: None, stderr: None,
show_hint: false, show_hint: false,
show_done: false, done_status: DoneStatus::Pending,
manual_run, manual_run,
} }
} }
@ -49,13 +56,18 @@ impl<'a> WatchState<'a> {
if output.status.success() { if output.status.success() {
self.stderr = None; self.stderr = None;
self.show_done = true; self.done_status =
if let Some(solution_path) = self.app_state.current_solution_path()? {
DoneStatus::DoneWithSolution(solution_path)
} else {
DoneStatus::DoneWithoutSolution
};
} else { } else {
self.app_state self.app_state
.set_pending(self.app_state.current_exercise_ind())?; .set_pending(self.app_state.current_exercise_ind())?;
self.stderr = Some(output.stderr); self.stderr = Some(output.stderr);
self.show_done = false; self.done_status = DoneStatus::Pending;
} }
self.render() self.render()
@ -67,7 +79,7 @@ impl<'a> WatchState<'a> {
} }
pub fn next_exercise(&mut self) -> Result<ExercisesProgress> { pub fn next_exercise(&mut self) -> Result<ExercisesProgress> {
if !self.show_done { if matches!(self.done_status, DoneStatus::Pending) {
self.writer self.writer
.write_all(b"The current exercise isn't done yet\n")?; .write_all(b"The current exercise isn't done yet\n")?;
self.show_prompt()?; self.show_prompt()?;
@ -84,7 +96,7 @@ impl<'a> WatchState<'a> {
self.writer.write_fmt(format_args!("{}un/", 'r'.bold()))?; self.writer.write_fmt(format_args!("{}un/", 'r'.bold()))?;
} }
if self.show_done { if !matches!(self.done_status, DoneStatus::Pending) {
self.writer.write_fmt(format_args!("{}ext/", 'n'.bold()))?; self.writer.write_fmt(format_args!("{}ext/", 'n'.bold()))?;
} }
@ -124,7 +136,7 @@ impl<'a> WatchState<'a> {
))?; ))?;
} }
if self.show_done { if !matches!(self.done_status, DoneStatus::Pending) {
self.writer.write_fmt(format_args!( self.writer.write_fmt(format_args!(
"{}\n\n", "{}\n\n",
"Exercise done ✓ "Exercise done ✓
@ -134,6 +146,13 @@ When you are done experimenting, enter `n` or `next` to go to the next exercise
))?; ))?;
} }
if let DoneStatus::DoneWithSolution(solution_path) = &self.done_status {
self.writer.write_fmt(format_args!(
"A solution file can be found at {}\n\n",
style(TerminalFileLink(solution_path)).underlined().green()
))?;
}
let line_width = size()?.0; let line_width = size()?.0;
let progress_bar = progress_bar( let progress_bar = progress_bar(
self.app_state.n_done(), self.app_state.n_done(),