Skip to content
15 changes: 13 additions & 2 deletions src/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -68,6 +69,7 @@ impl AppState {
pub fn new(
exercise_infos: Vec<ExerciseInfo>,
final_message: &'static str,
base_url: Option<String>,
editor: Option<Editor>,
vs_code_term: bool,
) -> Result<(Self, StateFileStatus)> {
Expand All @@ -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 {
Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -617,7 +628,7 @@ mod tests {
canonical_path: None,
test: false,
strict_clippy: false,
hint: "",
hint: String::new(),
done: false,
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
}

#[derive(Subcommand)]
Expand Down
2 changes: 1 addition & 1 deletion src/exercise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ pub struct Exercise {
pub canonical_path: Option<String>,
pub test: bool,
pub strict_clippy: bool,
pub hint: &'static str,
pub hint: String,
pub done: bool,
}

Expand Down
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ mod init;
mod list;
mod run;
mod term;
mod url_replacer;
mod watch;

const CURRENT_FORMAT_VERSION: u8 = 1;
Expand Down Expand Up @@ -71,6 +72,7 @@ fn main() -> Result<ExitCode> {
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,
)?;
Expand Down
67 changes: 67 additions & 0 deletions src/url_replacer.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading