mirror of
https://github.com/notohh/rustlings.git
synced 2024-11-25 23:04:17 -05:00
Dump solution and show its path
This commit is contained in:
parent
edf5762612
commit
8a085a0a85
7 changed files with 104 additions and 52 deletions
|
@ -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)?;
|
||||||
|
|
||||||
|
|
|
@ -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,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
14
src/run.rs
14
src/run.rs
|
@ -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
23
src/terminal_link.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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(),
|
||||||
|
|
Loading…
Reference in a new issue