diff --git a/src/app_state.rs b/src/app_state.rs index 5979150f..1c623075 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: String, + base_url: Option, ) -> Result<(Self, StateFileStatus)> { let cmd_runner = CmdRunner::build()?; let mut state_file = OpenOptions::new() @@ -80,6 +82,9 @@ 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() @@ -90,7 +95,11 @@ impl AppState { let path = exercise_info.path().leak(); let name = exercise_info.name.leak(); let dir = exercise_info.dir.map(|dir| &*dir.leak()); - let hint = exercise_info.hint.leak().trim_ascii(); + let mut hint = exercise_info.hint.leak().trim_ascii(); + + if let Some(replacer) = &url_replacer { + hint = replacer.replace(hint).leak(); + } let canonical_path = dir_canonical_path.as_deref().map(|dir_canonical_path| { let mut canonical_path; diff --git a/src/main.rs b/src/main.rs index eeb1883e..a46f9fa1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,6 +21,7 @@ mod init; mod list; mod run; mod term; +mod url_replacer; mod watch; const CURRENT_FORMAT_VERSION: u8 = 1; @@ -35,6 +36,10 @@ struct Args { /// Only use this if Rustlings fails to detect exercise file changes. #[arg(long)] manual_run: bool, + + /// Change rustbook url to passed one. + #[arg(long)] + base_url: Option, } #[derive(Subcommand)] @@ -94,6 +99,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, )?; // Show the welcome message if the state file doesn't exist yet. 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); + } +}