summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/embedded.rs23
-rw-r--r--src/exercise.rs2
-rw-r--r--src/main.rs67
-rw-r--r--src/project.rs20
-rw-r--r--src/run.rs16
5 files changed, 74 insertions, 54 deletions
diff --git a/src/embedded.rs b/src/embedded.rs
index 8f6c14e..25dbe64 100644
--- a/src/embedded.rs
+++ b/src/embedded.rs
@@ -52,7 +52,9 @@ impl EmbeddedFlatDir {
}
}
- self.readme.write_to_disk(WriteStrategy::Overwrite)
+ self.readme.write_to_disk(WriteStrategy::Overwrite)?;
+
+ Ok(())
}
}
@@ -63,16 +65,31 @@ struct ExercisesDir {
}
pub struct EmbeddedFiles {
- info_toml_content: &'static str,
+ pub info_toml_content: &'static str,
exercises_dir: ExercisesDir,
}
impl EmbeddedFiles {
pub fn init_exercises_dir(&self) -> io::Result<()> {
create_dir("exercises")?;
+
self.exercises_dir
.readme
- .write_to_disk(WriteStrategy::Overwrite)
+ .write_to_disk(WriteStrategy::IfNotExists)?;
+
+ for file in self.exercises_dir.files {
+ file.write_to_disk(WriteStrategy::IfNotExists)?;
+ }
+
+ for dir in self.exercises_dir.dirs {
+ dir.init_on_disk()?;
+
+ for file in dir.content {
+ file.write_to_disk(WriteStrategy::IfNotExists)?;
+ }
+ }
+
+ Ok(())
}
pub fn write_exercise_to_disk(&self, path: &Path, strategy: WriteStrategy) -> io::Result<()> {
diff --git a/src/exercise.rs b/src/exercise.rs
index 16e4a41..7c2e5fd 100644
--- a/src/exercise.rs
+++ b/src/exercise.rs
@@ -36,7 +36,7 @@ fn temp_file() -> String {
.filter(|c| c.is_alphanumeric())
.collect();
- format!("temp_{}_{thread_id}", process::id())
+ format!("./temp_{}_{thread_id}", process::id())
}
// The mode of the exercise.
diff --git a/src/main.rs b/src/main.rs
index 90d0109..822cd1a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -5,14 +5,14 @@ use crate::verify::verify;
use anyhow::Result;
use clap::{Parser, Subcommand};
use console::Emoji;
+use embedded::EMBEDDED_FILES;
use notify_debouncer_mini::notify::{self, RecursiveMode};
use notify_debouncer_mini::{new_debouncer, DebouncedEventKind};
use shlex::Shlex;
use std::ffi::OsStr;
-use std::fs;
-use std::io::{self, prelude::*};
+use std::io::{self, prelude::*, stdin, stdout};
use std::path::Path;
-use std::process::Command;
+use std::process::{exit, Command};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{channel, RecvTimeoutError};
use std::sync::{Arc, Mutex};
@@ -54,7 +54,7 @@ enum Subcommands {
/// The name of the exercise
name: String,
},
- /// Reset a single exercise using "git stash -- <filename>"
+ /// Reset a single exercise
Reset {
/// The name of the exercise
name: String,
@@ -83,13 +83,45 @@ enum Subcommands {
#[arg(short, long)]
solved: bool,
},
- /// Enable rust-analyzer for exercises
- Lsp,
}
fn main() -> Result<()> {
let args = Args::parse();
+ let exercises = toml_edit::de::from_str::<ExerciseList>(EMBEDDED_FILES.info_toml_content)
+ .unwrap()
+ .exercises;
+
+ if !Path::new("exercises").is_dir() {
+ let mut stdout = stdout().lock();
+ write!(
+ stdout,
+ "The `exercises` directory wasn't found in the current directory.
+Do you want to initialize Rustlings in the current directory (y/n)? "
+ )?;
+ stdout.flush()?;
+ let mut answer = String::new();
+ stdin().read_line(&mut answer)?;
+ answer.make_ascii_lowercase();
+ if answer.trim() != "y" {
+ exit(1);
+ }
+
+ EMBEDDED_FILES.init_exercises_dir()?;
+ if let Err(e) = write_project_json(&exercises) {
+ writeln!(
+ stdout,
+ "Failed to write rust-project.json to disk for rust-analyzer: {e}"
+ )?;
+ } else {
+ writeln!(stdout, "Successfully generated rust-project.json")?;
+ writeln!(
+ stdout,
+ "rust-analyzer will now parse exercises, restart your language server or editor"
+ )?;
+ }
+ }
+
if args.command.is_none() {
println!("\n{WELCOME}\n");
}
@@ -101,18 +133,6 @@ fn main() -> Result<()> {
std::process::exit(1);
}
- let info_file = fs::read_to_string("info.toml").unwrap_or_else(|e| {
- match e.kind() {
- io::ErrorKind::NotFound => println!(
- "The program must be run from the rustlings directory\nTry `cd rustlings/`!",
- ),
- _ => println!("Failed to read the info.toml file: {e}"),
- }
- std::process::exit(1);
- });
- let exercises = toml_edit::de::from_str::<ExerciseList>(&info_file)
- .unwrap()
- .exercises;
let verbose = args.nocapture;
let command = args.command.unwrap_or_else(|| {
@@ -205,7 +225,7 @@ fn main() -> Result<()> {
Subcommands::Reset { name } => {
let exercise = find_exercise(&name, &exercises);
- reset(exercise).unwrap_or_else(|_| std::process::exit(1));
+ reset(exercise)?;
}
Subcommands::Hint { name } => {
@@ -219,15 +239,6 @@ fn main() -> Result<()> {
.unwrap_or_else(|_| std::process::exit(1));
}
- Subcommands::Lsp => {
- if let Err(e) = write_project_json(exercises) {
- println!("Failed to write rust-project.json to disk for rust-analyzer: {e}");
- } else {
- println!("Successfully generated rust-project.json");
- println!("rust-analyzer will now parse exercises, restart your language server or editor");
- }
- }
-
Subcommands::Watch { success_hints } => match watch(&exercises, verbose, success_hints) {
Err(e) => {
println!("Error: Could not watch your progress. Error message was {e:?}.");
diff --git a/src/project.rs b/src/project.rs
index 0f56de9..bb6caa5 100644
--- a/src/project.rs
+++ b/src/project.rs
@@ -1,7 +1,7 @@
use anyhow::{Context, Result};
use serde::Serialize;
use std::env;
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use crate::exercise::Exercise;
@@ -9,14 +9,14 @@ use crate::exercise::Exercise;
/// Contains the structure of resulting rust-project.json file
/// and functions to build the data required to create the file
#[derive(Serialize)]
-struct RustAnalyzerProject {
+struct RustAnalyzerProject<'a> {
sysroot_src: PathBuf,
- crates: Vec<Crate>,
+ crates: Vec<Crate<'a>>,
}
#[derive(Serialize)]
-struct Crate {
- root_module: PathBuf,
+struct Crate<'a> {
+ root_module: &'a Path,
edition: &'static str,
// Not used, but required in the JSON file.
deps: Vec<()>,
@@ -25,12 +25,12 @@ struct Crate {
cfg: [&'static str; 1],
}
-impl RustAnalyzerProject {
- fn build(exercises: Vec<Exercise>) -> Result<Self> {
+impl<'a> RustAnalyzerProject<'a> {
+ fn build(exercises: &'a [Exercise]) -> Result<Self> {
let crates = exercises
- .into_iter()
+ .iter()
.map(|exercise| Crate {
- root_module: exercise.path,
+ root_module: &exercise.path,
edition: "2021",
deps: Vec::new(),
// This allows rust_analyzer to work inside `#[test]` blocks
@@ -69,7 +69,7 @@ impl RustAnalyzerProject {
}
/// Write `rust-project.json` to disk.
-pub fn write_project_json(exercises: Vec<Exercise>) -> Result<()> {
+pub fn write_project_json(exercises: &[Exercise]) -> Result<()> {
let content = RustAnalyzerProject::build(exercises)?;
// Using the capacity 2^14 since the file length in bytes is higher than 2^13.
diff --git a/src/run.rs b/src/run.rs
index 6dd0388..792bd8f 100644
--- a/src/run.rs
+++ b/src/run.rs
@@ -1,6 +1,7 @@
-use std::process::Command;
+use std::io;
use std::time::Duration;
+use crate::embedded::{WriteStrategy, EMBEDDED_FILES};
use crate::exercise::{Exercise, Mode};
use crate::verify::test;
use indicatif::ProgressBar;
@@ -19,17 +20,8 @@ pub fn run(exercise: &Exercise, verbose: bool) -> Result<(), ()> {
}
// Resets the exercise by stashing the changes.
-pub fn reset(exercise: &Exercise) -> Result<(), ()> {
- let command = Command::new("git")
- .arg("stash")
- .arg("--")
- .arg(&exercise.path)
- .spawn();
-
- match command {
- Ok(_) => Ok(()),
- Err(_) => Err(()),
- }
+pub fn reset(exercise: &Exercise) -> io::Result<()> {
+ EMBEDDED_FILES.write_exercise_to_disk(&exercise.path, WriteStrategy::Overwrite)
}
// Invoke the rust compiler on the path of the given exercise