mirror of
https://github.com/notohh/rustlings.git
synced 2024-11-26 07:14:17 -05:00
POC done
This commit is contained in:
parent
5b4103bbac
commit
3ff9b0cd2a
5 changed files with 74 additions and 54 deletions
|
@ -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 {
|
pub struct EmbeddedFiles {
|
||||||
info_toml_content: &'static str,
|
pub info_toml_content: &'static str,
|
||||||
exercises_dir: ExercisesDir,
|
exercises_dir: ExercisesDir,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EmbeddedFiles {
|
impl EmbeddedFiles {
|
||||||
pub fn init_exercises_dir(&self) -> io::Result<()> {
|
pub fn init_exercises_dir(&self) -> io::Result<()> {
|
||||||
create_dir("exercises")?;
|
create_dir("exercises")?;
|
||||||
|
|
||||||
self.exercises_dir
|
self.exercises_dir
|
||||||
.readme
|
.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<()> {
|
pub fn write_exercise_to_disk(&self, path: &Path, strategy: WriteStrategy) -> io::Result<()> {
|
||||||
|
|
|
@ -36,7 +36,7 @@ fn temp_file() -> String {
|
||||||
.filter(|c| c.is_alphanumeric())
|
.filter(|c| c.is_alphanumeric())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
format!("temp_{}_{thread_id}", process::id())
|
format!("./temp_{}_{thread_id}", process::id())
|
||||||
}
|
}
|
||||||
|
|
||||||
// The mode of the exercise.
|
// The mode of the exercise.
|
||||||
|
|
67
src/main.rs
67
src/main.rs
|
@ -5,14 +5,14 @@ use crate::verify::verify;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use console::Emoji;
|
use console::Emoji;
|
||||||
|
use embedded::EMBEDDED_FILES;
|
||||||
use notify_debouncer_mini::notify::{self, RecursiveMode};
|
use notify_debouncer_mini::notify::{self, RecursiveMode};
|
||||||
use notify_debouncer_mini::{new_debouncer, DebouncedEventKind};
|
use notify_debouncer_mini::{new_debouncer, DebouncedEventKind};
|
||||||
use shlex::Shlex;
|
use shlex::Shlex;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fs;
|
use std::io::{self, prelude::*, stdin, stdout};
|
||||||
use std::io::{self, prelude::*};
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::Command;
|
use std::process::{exit, Command};
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::mpsc::{channel, RecvTimeoutError};
|
use std::sync::mpsc::{channel, RecvTimeoutError};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
@ -54,7 +54,7 @@ enum Subcommands {
|
||||||
/// The name of the exercise
|
/// The name of the exercise
|
||||||
name: String,
|
name: String,
|
||||||
},
|
},
|
||||||
/// Reset a single exercise using "git stash -- <filename>"
|
/// Reset a single exercise
|
||||||
Reset {
|
Reset {
|
||||||
/// The name of the exercise
|
/// The name of the exercise
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -83,13 +83,45 @@ enum Subcommands {
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
solved: bool,
|
solved: bool,
|
||||||
},
|
},
|
||||||
/// Enable rust-analyzer for exercises
|
|
||||||
Lsp,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let args = Args::parse();
|
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() {
|
if args.command.is_none() {
|
||||||
println!("\n{WELCOME}\n");
|
println!("\n{WELCOME}\n");
|
||||||
}
|
}
|
||||||
|
@ -101,18 +133,6 @@ fn main() -> Result<()> {
|
||||||
std::process::exit(1);
|
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 verbose = args.nocapture;
|
||||||
|
|
||||||
let command = args.command.unwrap_or_else(|| {
|
let command = args.command.unwrap_or_else(|| {
|
||||||
|
@ -205,7 +225,7 @@ fn main() -> Result<()> {
|
||||||
Subcommands::Reset { name } => {
|
Subcommands::Reset { name } => {
|
||||||
let exercise = find_exercise(&name, &exercises);
|
let exercise = find_exercise(&name, &exercises);
|
||||||
|
|
||||||
reset(exercise).unwrap_or_else(|_| std::process::exit(1));
|
reset(exercise)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Subcommands::Hint { name } => {
|
Subcommands::Hint { name } => {
|
||||||
|
@ -219,15 +239,6 @@ fn main() -> Result<()> {
|
||||||
.unwrap_or_else(|_| std::process::exit(1));
|
.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) {
|
Subcommands::Watch { success_hints } => match watch(&exercises, verbose, success_hints) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Error: Could not watch your progress. Error message was {e:?}.");
|
println!("Error: Could not watch your progress. Error message was {e:?}.");
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
use crate::exercise::Exercise;
|
use crate::exercise::Exercise;
|
||||||
|
@ -9,14 +9,14 @@ use crate::exercise::Exercise;
|
||||||
/// Contains the structure of resulting rust-project.json file
|
/// Contains the structure of resulting rust-project.json file
|
||||||
/// and functions to build the data required to create the file
|
/// and functions to build the data required to create the file
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct RustAnalyzerProject {
|
struct RustAnalyzerProject<'a> {
|
||||||
sysroot_src: PathBuf,
|
sysroot_src: PathBuf,
|
||||||
crates: Vec<Crate>,
|
crates: Vec<Crate<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct Crate {
|
struct Crate<'a> {
|
||||||
root_module: PathBuf,
|
root_module: &'a Path,
|
||||||
edition: &'static str,
|
edition: &'static str,
|
||||||
// Not used, but required in the JSON file.
|
// Not used, but required in the JSON file.
|
||||||
deps: Vec<()>,
|
deps: Vec<()>,
|
||||||
|
@ -25,12 +25,12 @@ struct Crate {
|
||||||
cfg: [&'static str; 1],
|
cfg: [&'static str; 1],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RustAnalyzerProject {
|
impl<'a> RustAnalyzerProject<'a> {
|
||||||
fn build(exercises: Vec<Exercise>) -> Result<Self> {
|
fn build(exercises: &'a [Exercise]) -> Result<Self> {
|
||||||
let crates = exercises
|
let crates = exercises
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|exercise| Crate {
|
.map(|exercise| Crate {
|
||||||
root_module: exercise.path,
|
root_module: &exercise.path,
|
||||||
edition: "2021",
|
edition: "2021",
|
||||||
deps: Vec::new(),
|
deps: Vec::new(),
|
||||||
// This allows rust_analyzer to work inside `#[test]` blocks
|
// This allows rust_analyzer to work inside `#[test]` blocks
|
||||||
|
@ -69,7 +69,7 @@ impl RustAnalyzerProject {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write `rust-project.json` to disk.
|
/// 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)?;
|
let content = RustAnalyzerProject::build(exercises)?;
|
||||||
|
|
||||||
// Using the capacity 2^14 since the file length in bytes is higher than 2^13.
|
// Using the capacity 2^14 since the file length in bytes is higher than 2^13.
|
||||||
|
|
16
src/run.rs
16
src/run.rs
|
@ -1,6 +1,7 @@
|
||||||
use std::process::Command;
|
use std::io;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::embedded::{WriteStrategy, EMBEDDED_FILES};
|
||||||
use crate::exercise::{Exercise, Mode};
|
use crate::exercise::{Exercise, Mode};
|
||||||
use crate::verify::test;
|
use crate::verify::test;
|
||||||
use indicatif::ProgressBar;
|
use indicatif::ProgressBar;
|
||||||
|
@ -19,17 +20,8 @@ pub fn run(exercise: &Exercise, verbose: bool) -> Result<(), ()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resets the exercise by stashing the changes.
|
// Resets the exercise by stashing the changes.
|
||||||
pub fn reset(exercise: &Exercise) -> Result<(), ()> {
|
pub fn reset(exercise: &Exercise) -> io::Result<()> {
|
||||||
let command = Command::new("git")
|
EMBEDDED_FILES.write_exercise_to_disk(&exercise.path, WriteStrategy::Overwrite)
|
||||||
.arg("stash")
|
|
||||||
.arg("--")
|
|
||||||
.arg(&exercise.path)
|
|
||||||
.spawn();
|
|
||||||
|
|
||||||
match command {
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(_) => Err(()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invoke the rust compiler on the path of the given exercise
|
// Invoke the rust compiler on the path of the given exercise
|
||||||
|
|
Loading…
Reference in a new issue