diff --git a/src/dev.rs b/src/dev.rs index 40382a87..e09996f8 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{Context, Result}; use clap::Subcommand; mod check; @@ -13,8 +13,11 @@ pub enum DevCommands { impl DevCommands { pub fn run(self) -> Result<()> { match self { - DevCommands::Init => init::init(), + DevCommands::Init => init::init().context(INIT_ERR), DevCommands::Check => check::check(), } } } + +const INIT_ERR: &str = "Initialization failed. +After resolving the issue, delete the `rustlings` directory (if it was created) and try again"; diff --git a/src/dev/init.rs b/src/dev/init.rs index 01cfd9f0..d3821362 100644 --- a/src/dev/init.rs +++ b/src/dev/init.rs @@ -1,5 +1,101 @@ -use anyhow::Result; +use std::fs::{self, create_dir}; + +use anyhow::{Context, Result}; + +use crate::CURRENT_FORMAT_VERSION; pub fn init() -> Result<()> { - todo!() + create_dir("rustlings").context("Failed to create the directory `rustlings`")?; + + create_dir("rustlings/exercises") + .context("Failed to create the directory `rustlings/exercises`")?; + + create_dir("rustlings/solutions") + .context("Failed to create the directory `rustlings/solutions`")?; + + fs::write( + "rustlings/info.toml", + format!("{INFO_FILE_BEFORE_FORMAT_VERSION}{CURRENT_FORMAT_VERSION}{INFO_FILE_AFTER_FORMAT_VERSION}"), + ) + .context("Failed to create the file `rustlings/info.toml`")?; + + fs::write( + "rustligns/Cargo.toml", + format!("{CARGO_TOML_COMMENT}{}", crate::init::CARGO_TOML_PACKAGE), + ) + .context("Failed to create the file `rustlings/Cargo.toml`")?; + + fs::write("rustlings/.gitignore", crate::init::GITIGNORE) + .context("Failed to create the file `rustlings/.gitignore`")?; + + fs::write("rustlings/README.md", README) + .context("Failed to create the file `rustlings/README.md`")?; + + create_dir("rustlings/.vscode") + .context("Failed to create the directory `rustligns/.vscode`")?; + fs::write( + "rustlings/.vscode/extensions.json", + crate::init::VS_CODE_EXTENSIONS_JSON, + ) + .context("Failed to create the file `rustlings/.vscode/extensions.json`")?; + + println!("{INIT_DONE}"); + + Ok(()) } + +const INFO_FILE_BEFORE_FORMAT_VERSION: &str = + "# The format version is an indicator of the compatibility of third-party exercises with the +# Rustlings program. +# The format version is not the same as the version of the Rustlings program. +# In case Rustlings makes an unavoidable breaking change to the expected format of third-party +# exercises, you would need to raise this version and adapt to the new format. +# Otherwise, the newest version of the Rustlings program won't be able to run these exercises. +format_version = "; + +const INFO_FILE_AFTER_FORMAT_VERSION: &str = r#" + +# Optional multi-line message to be shown to users when just starting with the exercises. +welcome_message = """Welcome to these third-party Rustlings exercises.""" + +# Optional multi-line message to be shown to users after finishing all exercises. +final_message = """We hope that you found the exercises helpful :D""" + +# Repeat this section for every exercise. +[[exercises]] +# Exercise name which is the exercise file name without the `.rs` extension. +name = "???" + +# Optional directory name to be provided if you want to organize exercises in directories. +# If `dir` is specified, the exercise path is `exercises/DIR/NAME.rs` +# Otherwise, the path is `exercises/NAME.rs` +# dir = "???" + +# A mutli-line hint to be shown to users on request. +hint = """???""" +"#; + +const CARGO_TOML_COMMENT: &str = + "# You shouldn't edit this file manually! It is updated by `rustlings dev check` + +"; + +const README: &str = "# Rustlings 🦀 + +Welcome to these third-party Rustlings exercises 😃 + +First, +[install Rustlings using the official instructions in the README of the Rustlings project](https://github.com/rust-lang/rustlings) ✅ + +Then, open your terminal in this directory and run `rustlings` to get started with the exercises 🚀 +"; + +const INIT_DONE: &str = r#"Initialization done! +You can start developing third-party Rustlings exercises in the `rustlings` directory :D + +If the initialization was done in a Rust project which is a Cargo workspace, you need to add the +path to the `rustlings` directory to the `workspace.exclude` list in the project's `Cargo.toml` +file. For example: + +[workspace] +exclude = ["rustlings"]"#; diff --git a/src/init.rs b/src/init.rs index 459519de..32020171 100644 --- a/src/init.rs +++ b/src/init.rs @@ -1,14 +1,14 @@ use anyhow::{bail, Context, Result}; use std::{ env::set_current_dir, - fs::{create_dir, OpenOptions}, - io::{self, ErrorKind, Write}, + fs::{self, create_dir}, + io::ErrorKind, path::Path, }; use crate::{embedded::EMBEDDED_FILES, info_file::ExerciseInfo}; -fn create_cargo_toml(exercise_infos: &[ExerciseInfo]) -> io::Result<()> { +fn cargo_toml(exercise_infos: &[ExerciseInfo]) -> Vec { let mut cargo_toml = Vec::with_capacity(1 << 13); cargo_toml.extend_from_slice(b"bin = [\n"); for exercise_info in exercise_infos { @@ -23,39 +23,10 @@ fn create_cargo_toml(exercise_infos: &[ExerciseInfo]) -> io::Result<()> { cargo_toml.extend_from_slice(b".rs\" },\n"); } - cargo_toml.extend_from_slice( - br#"] + cargo_toml.extend_from_slice(b"]\n\n"); + cargo_toml.extend_from_slice(CARGO_TOML_PACKAGE.as_bytes()); -[package] -name = "rustlings" -edition = "2021" -publish = false -"#, - ); - OpenOptions::new() - .create_new(true) - .write(true) - .open("Cargo.toml")? - .write_all(&cargo_toml) -} - -fn create_gitignore() -> io::Result<()> { - OpenOptions::new() - .create_new(true) - .write(true) - .open(".gitignore")? - .write_all(GITIGNORE) -} - -fn create_vscode_dir() -> Result<()> { - create_dir(".vscode").context("Failed to create the directory `.vscode`")?; - OpenOptions::new() - .create_new(true) - .write(true) - .open(".vscode/extensions.json")? - .write_all(VS_CODE_EXTENSIONS_JSON)?; - - Ok(()) + cargo_toml } pub fn init(exercise_infos: &[ExerciseInfo]) -> Result<()> { @@ -78,21 +49,31 @@ pub fn init(exercise_infos: &[ExerciseInfo]) -> Result<()> { .init_exercises_dir() .context("Failed to initialize the `rustlings/exercises` directory")?; - create_cargo_toml(exercise_infos) + fs::write("Cargo.toml", cargo_toml(exercise_infos)) .context("Failed to create the file `rustlings/Cargo.toml`")?; - create_gitignore().context("Failed to create the file `rustlings/.gitignore`")?; + fs::write(".gitignore", GITIGNORE) + .context("Failed to create the file `rustlings/.gitignore`")?; - create_vscode_dir().context("Failed to create the file `rustlings/.vscode/extensions.json`")?; + create_dir(".vscode").context("Failed to create the directory `rustlings/.vscode`")?; + fs::write(".vscode/extensions.json", VS_CODE_EXTENSIONS_JSON) + .context("Failed to create the file `rustlings/.vscode/extensions.json`")?; Ok(()) } -const GITIGNORE: &[u8] = b"/target -/.rustlings-state.txt +pub const CARGO_TOML_PACKAGE: &str = r#"[package] +name = "rustlings" +edition = "2021" +publish = false +"#; + +pub const GITIGNORE: &[u8] = b"Cargo.lock +.rustlings-state.txt +target "; -const VS_CODE_EXTENSIONS_JSON: &[u8] = br#"{"recommendations":["rust-lang.rust-analyzer"]}"#; +pub const VS_CODE_EXTENSIONS_JSON: &[u8] = br#"{"recommendations":["rust-lang.rust-analyzer"]}"#; const PROBABLY_IN_RUSTLINGS_DIR_ERR: &str = "A directory with the name `exercises` and a file with the name `Cargo.toml` already exist