diff --git a/src/init.rs b/src/init.rs index 3970bb2f..dc23cbbf 100644 --- a/src/init.rs +++ b/src/init.rs @@ -3,30 +3,40 @@ use ratatui::crossterm::style::Stylize; use std::{ env::set_current_dir, fs::{self, create_dir}, - io::ErrorKind, + io::{self, Write}, path::Path, process::{Command, Stdio}, }; -use crate::{cargo_toml::updated_cargo_toml, embedded::EMBEDDED_FILES, info_file::InfoFile}; +use crate::{ + cargo_toml::updated_cargo_toml, embedded::EMBEDDED_FILES, info_file::InfoFile, + term::press_enter_prompt, +}; pub fn init() -> Result<()> { - // Prevent initialization in a directory that contains the file `Cargo.toml`. - // This can mean that Rustlings was already initialized in this directory. - // Otherwise, this can cause problems with Cargo workspaces. + let rustlings_dir = Path::new("rustlings"); + if rustlings_dir.exists() { + bail!(RUSTLINGS_DIR_ALREADY_EXISTS_ERR); + } + + let mut stdout = io::stdout().lock(); + let mut init_git = true; + if Path::new("Cargo.toml").exists() { - bail!(CARGO_TOML_EXISTS_ERR); - } - - let rustlings_path = Path::new("rustlings"); - if let Err(e) = create_dir(rustlings_path) { - if e.kind() == ErrorKind::AlreadyExists { - bail!(RUSTLINGS_DIR_ALREADY_EXISTS_ERR); + if Path::new("exercises").exists() && Path::new("solutions").exists() { + bail!(IN_INITIALIZED_DIR_ERR); } - return Err(e.into()); + + stdout.write_all(CARGO_TOML_EXISTS_PROMPT_MSG)?; + press_enter_prompt(&mut stdout)?; + init_git = false; } - set_current_dir("rustlings") + stdout.write_all(b"This command will create the directory `rustlings/` which will contain the exercises.\nPress ENTER to continue ")?; + press_enter_prompt(&mut stdout)?; + + create_dir(rustlings_dir).context("Failed to create the `rustlings/` directory")?; + set_current_dir(rustlings_dir) .context("Failed to change the current directory to `rustlings/`")?; let info_file = InfoFile::parse()?; @@ -75,18 +85,21 @@ pub fn init() -> Result<()> { fs::write(".vscode/extensions.json", VS_CODE_EXTENSIONS_JSON) .context("Failed to create the file `rustlings/.vscode/extensions.json`")?; - // Ignore any Git error because Git initialization is not required. - let _ = Command::new("git") - .arg("init") - .stdin(Stdio::null()) - .stderr(Stdio::null()) - .status(); + if init_git { + // Ignore any Git error because Git initialization is not required. + let _ = Command::new("git") + .arg("init") + .stdin(Stdio::null()) + .stderr(Stdio::null()) + .status(); + } - println!( + writeln!( + stdout, "\n{}\n\n{}", "Initialization done ✓".green(), POST_INIT_MSG.bold(), - ); + )?; Ok(()) } @@ -104,7 +117,7 @@ target/ pub const VS_CODE_EXTENSIONS_JSON: &[u8] = br#"{"recommendations":["rust-lang.rust-analyzer"]}"#; -const CARGO_TOML_EXISTS_ERR: &str = "The current directory contains the file `Cargo.toml`. +const IN_INITIALIZED_DIR_ERR: &str = "It looks like Rustlings is already initialized in this directory. If you already initialized Rustlings, run the command `rustlings` for instructions on getting started with the exercises. Otherwise, please run `rustlings init` again in another directory."; @@ -115,5 +128,19 @@ You probably already initialized Rustlings. Run `cd rustlings` Then run `rustlings` again"; +const CARGO_TOML_EXISTS_PROMPT_MSG: &[u8] = br#"You are about to initialize Rustlings in a directory that already contains a `Cargo.toml` file! + + => It is recommended to abort with CTRL+C and initialize Rustlings in another directory <= + +If you know what you are doing and want to initialize Rustlings in a Cargo workspace, +then you need to add its directory to `members` in the `workspace` section of the `Cargo.toml` file: + +```toml +[workspace] +members = ["rustlings"] +``` + +Press ENTER if you are sure that you want to continue after reading the warning above "#; + const POST_INIT_MSG: &str = "Run `cd rustlings` to go into the generated directory. Then run `rustlings` to get started."; diff --git a/src/main.rs b/src/main.rs index 12786d01..edb3e146 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,10 +2,11 @@ use anyhow::{bail, Context, Result}; use app_state::StateFileStatus; use clap::{Parser, Subcommand}; use std::{ - io::{self, BufRead, IsTerminal, StdoutLock, Write}, + io::{self, IsTerminal, Write}, path::Path, process::exit, }; +use term::{clear_terminal, press_enter_prompt}; use self::{app_state::AppState, dev::DevCommands, info_file::InfoFile, watch::WatchExit}; @@ -20,20 +21,12 @@ mod init; mod list; mod progress_bar; mod run; +mod term; mod terminal_link; mod watch; const CURRENT_FORMAT_VERSION: u8 = 1; -fn clear_terminal(stdout: &mut StdoutLock) -> io::Result<()> { - stdout.write_all(b"\x1b[H\x1b[2J\x1b[3J") -} - -fn press_enter_prompt() -> io::Result<()> { - io::stdin().lock().read_until(b'\n', &mut Vec::new())?; - Ok(()) -} - /// Rustlings is a collection of small exercises to get you used to writing and reading Rust code #[derive(Parser)] #[command(version)] @@ -79,14 +72,6 @@ fn main() -> Result<()> { match args.command { Some(Subcommands::Init) => { - { - let mut stdout = io::stdout().lock(); - stdout.write_all(b"This command will create the directory `rustlings/` which will contain the exercises.\nPress ENTER to continue ")?; - stdout.flush()?; - press_enter_prompt()?; - stdout.write_all(b"\n")?; - } - return init::init().context("Initialization failed"); } Some(Subcommands::Dev(dev_command)) => return dev_command.run(), @@ -118,8 +103,7 @@ fn main() -> Result<()> { let welcome_message = welcome_message.trim_ascii(); write!(stdout, "{welcome_message}\n\nPress ENTER to continue ")?; - stdout.flush()?; - press_enter_prompt()?; + press_enter_prompt(&mut stdout)?; clear_terminal(&mut stdout)?; // Flush to be able to show errors occuring before printing a newline to stdout. stdout.flush()?; diff --git a/src/term.rs b/src/term.rs new file mode 100644 index 00000000..e1ac3da9 --- /dev/null +++ b/src/term.rs @@ -0,0 +1,12 @@ +use std::io::{self, BufRead, StdoutLock, Write}; + +pub fn clear_terminal(stdout: &mut StdoutLock) -> io::Result<()> { + stdout.write_all(b"\x1b[H\x1b[2J\x1b[3J") +} + +pub fn press_enter_prompt(stdout: &mut StdoutLock) -> io::Result<()> { + stdout.flush()?; + io::stdin().lock().read_until(b'\n', &mut Vec::new())?; + stdout.write_all(b"\n")?; + Ok(()) +}