diff --git a/src/app_state.rs b/src/app_state.rs index 27c08b1d..2c419825 100644 --- a/src/app_state.rs +++ b/src/app_state.rs @@ -21,6 +21,7 @@ use crate::{ exercise::{Exercise, RunnableExercise}, info_file::ExerciseInfo, term::{self, CheckProgressVisualizer}, + url_replacer::UrlReplacer, }; const STATE_FILE_NAME: &str = ".rustlings-state.txt"; @@ -68,6 +69,7 @@ impl AppState { pub fn new( exercise_infos: Vec, final_message: &'static str, + base_url: Option, editor: Option, vs_code_term: bool, ) -> Result<(Self, StateFileStatus)> { @@ -82,10 +84,19 @@ impl AppState { format!("Failed to open or create the state file {STATE_FILE_NAME}") })?; + // replacer for rustbook url + let url_replacer = base_url.as_ref().map(|url| UrlReplacer::new(url)); + let dir_canonical_path = term::canonicalize("exercises"); let mut exercises = exercise_infos .into_iter() .map(|exercise_info| { + let hint = if let Some(replacer) = &url_replacer { + replacer.replace(exercise_info.hint.trim_ascii()) + } else { + exercise_info.hint.trim_ascii().to_string() + }; + let canonical_path = dir_canonical_path.as_deref().map(|dir_canonical_path| { let mut canonical_path; if let Some(dir) = exercise_info.dir { @@ -117,7 +128,7 @@ impl AppState { canonical_path, test: exercise_info.test, strict_clippy: exercise_info.strict_clippy, - hint: exercise_info.hint.trim_ascii(), + hint, // Updated below. done: false, } @@ -617,7 +628,7 @@ mod tests { canonical_path: None, test: false, strict_clippy: false, - hint: "", + hint: String::new(), done: false, } } diff --git a/src/cli.rs b/src/cli.rs index 153994be..14d483d1 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -26,6 +26,9 @@ pub struct Args { /// Only use this if Rustlings fails to detect exercise file changes #[arg(long)] pub manual_run: bool, + /// Replace the rustbook URL with the provided base URL. + #[arg(long)] + pub base_url: Option, } #[derive(Subcommand)] diff --git a/src/exercise.rs b/src/exercise.rs index b969c69a..7732e1a2 100644 --- a/src/exercise.rs +++ b/src/exercise.rs @@ -73,7 +73,7 @@ pub struct Exercise { pub canonical_path: Option, pub test: bool, pub strict_clippy: bool, - pub hint: &'static str, + pub hint: String, pub done: bool, } diff --git a/src/main.rs b/src/main.rs index 8da36f7f..d97a79f2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,6 +29,7 @@ mod init; mod list; mod run; mod term; +mod url_replacer; mod watch; const CURRENT_FORMAT_VERSION: u8 = 1; @@ -71,6 +72,7 @@ fn main() -> Result { let (mut app_state, state_file_status) = AppState::new( info_file.exercises, info_file.final_message.unwrap_or_default(), + args.base_url, editor, vs_code_term, )?; diff --git a/src/url_replacer.rs b/src/url_replacer.rs new file mode 100644 index 00000000..3a3537a5 --- /dev/null +++ b/src/url_replacer.rs @@ -0,0 +1,67 @@ +pub struct UrlReplacer { + base_url: String, +} + +const EN_BASE_URL: &str = "https://doc.rust-lang.org/book"; + +impl UrlReplacer { + /// this fn will trim url end with '/' + pub fn new(base_url: &str) -> Self { + let url = if base_url.ends_with('/') { + base_url.trim_end_matches('/').to_owned() + } else { + base_url.to_owned() + }; + + Self { base_url: url } + } + + /// replace rustbook url + pub fn replace(&self, hint: &str) -> String { + hint.replace(EN_BASE_URL, &self.base_url) + } +} + +#[cfg(test)] +mod test { + use super::*; + + const TEST_DOMAIN: &str = "https://doc.rust-kr.org"; + + #[test] + fn non_rustbook_url() { + let replacer = UrlReplacer::new(&String::from(TEST_DOMAIN)); + + let hint = "\ +hints (...) lines (...) +link: https://example.com/ch03-02-data-types.html"; + + assert_eq!(hint, replacer.replace(hint)); + } + + #[test] + fn replace_rustbook_url() { + let replacer = UrlReplacer::new(&String::from(TEST_DOMAIN)); + + let hint = "\ +hints (...) lines (...) +link: https://doc.rust-lang.org/book/ch03-02-data-types.html"; + + assert_eq!( + "\ +hints (...) lines (...) +link: https://doc.rust-kr.org/ch03-02-data-types.html", + replacer.replace(hint) + ); + } + + #[test] + fn trim_end_with_slash() { + let mut domain = String::from(TEST_DOMAIN); + domain.push('/'); + + let replacer = UrlReplacer::new(&domain); + + assert_eq!(TEST_DOMAIN, replacer.base_url); + } +}