Change approach to point to current exercise
This commit is contained in:
parent
13cdc1aa69
commit
47b2dfc7fe
|
@ -1,2 +0,0 @@
|
||||||
[alias]
|
|
||||||
generate-manifest = "run --features maintainer -- maintainer"
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -8,3 +8,4 @@ exercises/clippy/Cargo.lock
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
exercises/Cargo.lock
|
exercises/Cargo.lock
|
||||||
|
exercises/Cargo.toml
|
||||||
|
|
598
Cargo.lock
generated
598
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -4,10 +4,6 @@ version = "4.1.0"
|
||||||
authors = ["Marisa <mokou@posteo.de>", "Carol (Nichols || Goulding) <carol.nichols@gmail.com>"]
|
authors = ["Marisa <mokou@posteo.de>", "Carol (Nichols || Goulding) <carol.nichols@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[features]
|
|
||||||
# Internal tooling for maintainers
|
|
||||||
maintainer = ["tooling"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = "2.32.0"
|
clap = "2.32.0"
|
||||||
indicatif = "0.10.3"
|
indicatif = "0.10.3"
|
||||||
|
@ -16,7 +12,6 @@ notify = "4.0.15"
|
||||||
toml = "0.5.6"
|
toml = "0.5.6"
|
||||||
regex = "1.1.6"
|
regex = "1.1.6"
|
||||||
serde = {version = "1.0.10", features = ["derive"]}
|
serde = {version = "1.0.10", features = ["derive"]}
|
||||||
tooling = { path = "./tooling", optional = true }
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "rustlings"
|
name = "rustlings"
|
||||||
|
@ -26,4 +21,3 @@ path = "src/main.rs"
|
||||||
assert_cmd = "0.11.0"
|
assert_cmd = "0.11.0"
|
||||||
predicates = "1.0.1"
|
predicates = "1.0.1"
|
||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
||||||
tooling = { path = "./tooling" }
|
|
||||||
|
|
|
@ -104,11 +104,13 @@ rustlings hint myExercise1
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using an IDE
|
### Using an IDE
|
||||||
**TL,DR**: open the `exercises/` folder in your editor.
|
**TL,DR**: open the `exercises/` folder in your editor while running `rustlings watch`.
|
||||||
|
|
||||||
There are several editors and plugins that are able to analyze Rust code for a full blown IDE (or IDE-like) experience. The official language plugin of the Rust project is [rust-analyzer](rust-analyzer), but there are others, like [IntelliJ Rust](intellij) or [rls](rls).
|
There are several editors and plugins that are able to analyze Rust code for a full blown IDE (or IDE-like) experience. The official language plugin of the Rust project is [rust-analyzer](rust-analyzer), but there are others, like [IntelliJ Rust](intellij) or [rls](rls).
|
||||||
|
|
||||||
These tools usually rely on the default structure of a Cargo project to perform their analyses. Conversely, Rustlings is conceived as a collection of isolated exercises. Nevertheless, we auto-generate a "fake" project manifest in `exercises/Cargo.toml` that should bypass this mismatch. If you open the `exercises/` folder with your Rust-enabled editor, you should be able to enjoy its capabilities.
|
These tools usually rely on the default structure of a Cargo project to perform their analyses. Conversely, Rustlings has a _atypical_ structure: it's a collection of isolated exercises. Your editor might not be able to properly analyze the code of the exercises.
|
||||||
|
|
||||||
|
However, if you run `rustlings watch`, we auto-generate a "fake" project manifest in `exercises/Cargo.toml` that points to the currently active exercise. If you open the `exercises/` folder with your Rust-enabled editor, you should be able to enjoy its capabilities. Note that we update the manifest so it always points to the current exercise, so ideally your editor should be able to refresh the project metadata automatically.
|
||||||
|
|
||||||
[rust-analyzer]: https://rust-analyzer.github.io/
|
[rust-analyzer]: https://rust-analyzer.github.io/
|
||||||
[rls]: https://github.com/rust-lang/rls
|
[rls]: https://github.com/rust-lang/rls
|
||||||
|
|
|
@ -1,286 +0,0 @@
|
||||||
# This Cargo.toml is AUTOGENERATED, you shouldn't need to edit it.
|
|
||||||
# The `rustling` commands do not rely on this manifest at all, its only purpose
|
|
||||||
# is to help editors analyze your code.
|
|
||||||
# To regenerate it, run `cargo generate-manifest`.
|
|
||||||
[package]
|
|
||||||
name = 'exercises'
|
|
||||||
version = '0.1.0'
|
|
||||||
edition = '2018'
|
|
||||||
authors = ['The Rustlings Maintainers']
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'clippy1'
|
|
||||||
path = 'clippy/clippy1.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'clippy2'
|
|
||||||
path = 'clippy/clippy2.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'as_ref_mut'
|
|
||||||
path = 'conversions/as_ref_mut.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'from_into'
|
|
||||||
path = 'conversions/from_into.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'from_str'
|
|
||||||
path = 'conversions/from_str.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'try_from_into'
|
|
||||||
path = 'conversions/try_from_into.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'using_as'
|
|
||||||
path = 'conversions/using_as.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'enums1'
|
|
||||||
path = 'enums/enums1.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'enums2'
|
|
||||||
path = 'enums/enums2.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'enums3'
|
|
||||||
path = 'enums/enums3.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'errors1'
|
|
||||||
path = 'error_handling/errors1.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'errors2'
|
|
||||||
path = 'error_handling/errors2.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'errors3'
|
|
||||||
path = 'error_handling/errors3.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'errorsn'
|
|
||||||
path = 'error_handling/errorsn.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'result1'
|
|
||||||
path = 'error_handling/result1.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'functions1'
|
|
||||||
path = 'functions/functions1.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'functions2'
|
|
||||||
path = 'functions/functions2.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'functions3'
|
|
||||||
path = 'functions/functions3.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'functions4'
|
|
||||||
path = 'functions/functions4.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'functions5'
|
|
||||||
path = 'functions/functions5.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'generics1'
|
|
||||||
path = 'generics/generics1.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'generics2'
|
|
||||||
path = 'generics/generics2.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'generics3'
|
|
||||||
path = 'generics/generics3.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'if1'
|
|
||||||
path = 'if/if1.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'if2'
|
|
||||||
path = 'if/if2.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'macros1'
|
|
||||||
path = 'macros/macros1.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'macros2'
|
|
||||||
path = 'macros/macros2.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'macros3'
|
|
||||||
path = 'macros/macros3.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'macros4'
|
|
||||||
path = 'macros/macros4.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'modules1'
|
|
||||||
path = 'modules/modules1.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'modules2'
|
|
||||||
path = 'modules/modules2.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'move_semantics1'
|
|
||||||
path = 'move_semantics/move_semantics1.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'move_semantics2'
|
|
||||||
path = 'move_semantics/move_semantics2.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'move_semantics3'
|
|
||||||
path = 'move_semantics/move_semantics3.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'move_semantics4'
|
|
||||||
path = 'move_semantics/move_semantics4.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'option1'
|
|
||||||
path = 'option/option1.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'option2'
|
|
||||||
path = 'option/option2.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'primitive_types1'
|
|
||||||
path = 'primitive_types/primitive_types1.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'primitive_types2'
|
|
||||||
path = 'primitive_types/primitive_types2.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'primitive_types3'
|
|
||||||
path = 'primitive_types/primitive_types3.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'primitive_types4'
|
|
||||||
path = 'primitive_types/primitive_types4.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'primitive_types5'
|
|
||||||
path = 'primitive_types/primitive_types5.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'primitive_types6'
|
|
||||||
path = 'primitive_types/primitive_types6.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'quiz1'
|
|
||||||
path = 'quiz1.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'quiz2'
|
|
||||||
path = 'quiz2.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'quiz3'
|
|
||||||
path = 'quiz3.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'quiz4'
|
|
||||||
path = 'quiz4.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'arc1'
|
|
||||||
path = 'standard_library_types/arc1.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'box1'
|
|
||||||
path = 'standard_library_types/box1.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'iterators2'
|
|
||||||
path = 'standard_library_types/iterators2.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'iterators3'
|
|
||||||
path = 'standard_library_types/iterators3.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'iterators4'
|
|
||||||
path = 'standard_library_types/iterators4.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'strings1'
|
|
||||||
path = 'strings/strings1.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'strings2'
|
|
||||||
path = 'strings/strings2.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'structs1'
|
|
||||||
path = 'structs/structs1.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'structs2'
|
|
||||||
path = 'structs/structs2.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'structs3'
|
|
||||||
path = 'structs/structs3.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'tests1'
|
|
||||||
path = 'tests/tests1.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'tests2'
|
|
||||||
path = 'tests/tests2.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'tests3'
|
|
||||||
path = 'tests/tests3.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'threads1'
|
|
||||||
path = 'threads/threads1.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'traits1'
|
|
||||||
path = 'traits/traits1.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'traits2'
|
|
||||||
path = 'traits/traits2.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'variables1'
|
|
||||||
path = 'variables/variables1.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'variables2'
|
|
||||||
path = 'variables/variables2.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'variables3'
|
|
||||||
path = 'variables/variables3.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'variables4'
|
|
||||||
path = 'variables/variables4.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'variables5'
|
|
||||||
path = 'variables/variables5.rs'
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = 'variables6'
|
|
||||||
path = 'variables/variables6.rs'
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
root = "exercises"
|
||||||
|
|
||||||
# VARIABLES
|
# VARIABLES
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
use std::fs::{self, remove_file, File};
|
use std::fs::{self, remove_file};
|
||||||
use std::io::Read;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::{self, Command};
|
use std::process::{self, Command};
|
||||||
|
|
||||||
const RUSTC_COLOR_ARGS: &[&str] = &["--color", "always"];
|
const RUSTC_COLOR_ARGS: &[&str] = &["--color", "always"];
|
||||||
const I_AM_DONE_REGEX: &str = r"(?m)^\s*///?\s*I\s+AM\s+NOT\s+DONE";
|
const I_AM_DONE_REGEX: &str = r"(?m)^\s*///?\s*I\s+AM\s+NOT\s+DONE";
|
||||||
|
const MAIN_FN_REGEX: &str = r"(?m)^\s*fn\s+main\s*\(\s*\)";
|
||||||
const CONTEXT: usize = 2;
|
const CONTEXT: usize = 2;
|
||||||
const CLIPPY_CARGO_TOML_PATH: &str = "./exercises/clippy/Cargo.toml";
|
const CLIPPY_CARGO_TOML_PATH: &str = "./exercises/clippy/Cargo.toml";
|
||||||
|
|
||||||
|
@ -30,7 +30,8 @@ pub enum Mode {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct ExerciseList {
|
pub struct ExerciseInfo {
|
||||||
|
pub root: Option<String>,
|
||||||
pub exercises: Vec<Exercise>,
|
pub exercises: Vec<Exercise>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,17 +188,7 @@ path = "{}.rs""#,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn state(&self) -> State {
|
pub fn state(&self) -> State {
|
||||||
let mut source_file =
|
let source = self.source();
|
||||||
File::open(&self.path).expect("We were unable to open the exercise file!");
|
|
||||||
|
|
||||||
let source = {
|
|
||||||
let mut s = String::new();
|
|
||||||
source_file
|
|
||||||
.read_to_string(&mut s)
|
|
||||||
.expect("We were unable to read the exercise file!");
|
|
||||||
s
|
|
||||||
};
|
|
||||||
|
|
||||||
let re = Regex::new(I_AM_DONE_REGEX).unwrap();
|
let re = Regex::new(I_AM_DONE_REGEX).unwrap();
|
||||||
|
|
||||||
if !re.is_match(&source) {
|
if !re.is_match(&source) {
|
||||||
|
@ -227,6 +218,16 @@ path = "{}.rs""#,
|
||||||
|
|
||||||
State::Pending(context)
|
State::Pending(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_binary(&self) -> bool {
|
||||||
|
let source = self.source();
|
||||||
|
let re = Regex::new(MAIN_FN_REGEX).unwrap();
|
||||||
|
re.is_match(&source)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn source(&self) -> String {
|
||||||
|
fs::read_to_string(&self.path).expect("Unable to read exercise source")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Exercise {
|
impl Display for Exercise {
|
||||||
|
@ -243,7 +244,7 @@ fn clean() {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::path::Path;
|
use std::{fs::File, path::Path};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_clean() {
|
fn test_clean() {
|
||||||
|
|
46
src/main.rs
46
src/main.rs
|
@ -1,4 +1,4 @@
|
||||||
use crate::exercise::{Exercise, ExerciseList};
|
use crate::exercise::{Exercise, ExerciseInfo};
|
||||||
use crate::run::run;
|
use crate::run::run;
|
||||||
use crate::verify::verify;
|
use crate::verify::verify;
|
||||||
use clap::{crate_version, App, Arg, SubCommand};
|
use clap::{crate_version, App, Arg, SubCommand};
|
||||||
|
@ -19,11 +19,12 @@ use std::time::Duration;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
mod exercise;
|
mod exercise;
|
||||||
|
mod manifest;
|
||||||
mod run;
|
mod run;
|
||||||
mod verify;
|
mod verify;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let app = App::new("rustlings")
|
let matches = App::new("rustlings")
|
||||||
.version(crate_version!())
|
.version(crate_version!())
|
||||||
.author("Olivia Hugger, Carol Nichols")
|
.author("Olivia Hugger, Carol Nichols")
|
||||||
.about("Rustlings is a collection of small exercises to get you used to writing and reading Rust code")
|
.about("Rustlings is a collection of small exercises to get you used to writing and reading Rust code")
|
||||||
|
@ -53,18 +54,8 @@ fn main() {
|
||||||
.alias("h")
|
.alias("h")
|
||||||
.about("Returns a hint for the current exercise")
|
.about("Returns a hint for the current exercise")
|
||||||
.arg(Arg::with_name("name").required(true).index(1)),
|
.arg(Arg::with_name("name").required(true).index(1)),
|
||||||
);
|
)
|
||||||
|
.get_matches();
|
||||||
let matches;
|
|
||||||
#[cfg(feature = "maintainer")]
|
|
||||||
{
|
|
||||||
let app = app.subcommand(SubCommand::with_name("maintainer"));
|
|
||||||
matches = app.get_matches();
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "maintainer"))]
|
|
||||||
{
|
|
||||||
matches = app.get_matches();
|
|
||||||
}
|
|
||||||
|
|
||||||
if matches.subcommand_name().is_none() {
|
if matches.subcommand_name().is_none() {
|
||||||
println!();
|
println!();
|
||||||
|
@ -95,16 +86,9 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
let toml_str = &fs::read_to_string("info.toml").unwrap();
|
let toml_str = &fs::read_to_string("info.toml").unwrap();
|
||||||
let exercises = toml::from_str::<ExerciseList>(toml_str).unwrap().exercises;
|
let ExerciseInfo { exercises, root } = toml::from_str::<ExerciseInfo>(toml_str).unwrap();
|
||||||
let verbose = matches.is_present("nocapture");
|
let verbose = matches.is_present("nocapture");
|
||||||
|
|
||||||
#[cfg(feature = "maintainer")]
|
|
||||||
{
|
|
||||||
if matches.subcommand_matches("maintainer").is_some() {
|
|
||||||
maintainer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ref matches) = matches.subcommand_matches("run") {
|
if let Some(ref matches) = matches.subcommand_matches("run") {
|
||||||
let name = matches.value_of("name").unwrap();
|
let name = matches.value_of("name").unwrap();
|
||||||
|
|
||||||
|
@ -133,10 +117,12 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches.subcommand_matches("verify").is_some() {
|
if matches.subcommand_matches("verify").is_some() {
|
||||||
verify(&exercises, verbose).unwrap_or_else(|_| std::process::exit(1));
|
verify(&exercises, root.as_deref(), verbose).unwrap_or_else(|_| std::process::exit(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches.subcommand_matches("watch").is_some() && watch(&exercises, verbose).is_ok() {
|
if matches.subcommand_matches("watch").is_some()
|
||||||
|
&& watch(&exercises, root.as_deref(), verbose).is_ok()
|
||||||
|
{
|
||||||
println!(
|
println!(
|
||||||
"{emoji} All exercises completed! {emoji}",
|
"{emoji} All exercises completed! {emoji}",
|
||||||
emoji = Emoji("🎉", "★")
|
emoji = Emoji("🎉", "★")
|
||||||
|
@ -179,7 +165,7 @@ fn spawn_watch_shell(failed_exercise_hint: &Arc<Mutex<Option<String>>>) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn watch(exercises: &[Exercise], verbose: bool) -> notify::Result<()> {
|
fn watch(exercises: &[Exercise], root: Option<&str>, verbose: bool) -> notify::Result<()> {
|
||||||
/* Clears the terminal with an ANSI escape code.
|
/* Clears the terminal with an ANSI escape code.
|
||||||
Works in UNIX and newer Windows terminals. */
|
Works in UNIX and newer Windows terminals. */
|
||||||
fn clear_screen() {
|
fn clear_screen() {
|
||||||
|
@ -194,7 +180,7 @@ fn watch(exercises: &[Exercise], verbose: bool) -> notify::Result<()> {
|
||||||
clear_screen();
|
clear_screen();
|
||||||
|
|
||||||
let to_owned_hint = |t: &Exercise| t.hint.to_owned();
|
let to_owned_hint = |t: &Exercise| t.hint.to_owned();
|
||||||
let failed_exercise_hint = match verify(exercises.iter(), verbose) {
|
let failed_exercise_hint = match verify(exercises.iter(), root, verbose) {
|
||||||
Ok(_) => return Ok(()),
|
Ok(_) => return Ok(()),
|
||||||
Err(exercise) => Arc::new(Mutex::new(Some(to_owned_hint(exercise)))),
|
Err(exercise) => Arc::new(Mutex::new(Some(to_owned_hint(exercise)))),
|
||||||
};
|
};
|
||||||
|
@ -209,7 +195,7 @@ fn watch(exercises: &[Exercise], verbose: bool) -> notify::Result<()> {
|
||||||
.iter()
|
.iter()
|
||||||
.skip_while(|e| !filepath.ends_with(&e.path));
|
.skip_while(|e| !filepath.ends_with(&e.path));
|
||||||
clear_screen();
|
clear_screen();
|
||||||
match verify(pending_exercises, verbose) {
|
match verify(pending_exercises, root, verbose) {
|
||||||
Ok(_) => return Ok(()),
|
Ok(_) => return Ok(()),
|
||||||
Err(exercise) => {
|
Err(exercise) => {
|
||||||
let mut failed_exercise_hint = failed_exercise_hint.lock().unwrap();
|
let mut failed_exercise_hint = failed_exercise_hint.lock().unwrap();
|
||||||
|
@ -234,9 +220,3 @@ fn rustc_exists() -> bool {
|
||||||
.map(|status| status.success())
|
.map(|status| status.success())
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "maintainer")]
|
|
||||||
fn maintainer() {
|
|
||||||
let manifest = tooling::generate(&Path::new("exercises"));
|
|
||||||
fs::write("exercises/Cargo.toml", manifest).unwrap();
|
|
||||||
}
|
|
||||||
|
|
76
src/manifest.rs
Normal file
76
src/manifest.rs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
use serde::Serialize;
|
||||||
|
use toml;
|
||||||
|
|
||||||
|
use crate::exercise::Exercise;
|
||||||
|
|
||||||
|
pub fn update(exercise: &Exercise, root: Option<&str>) {
|
||||||
|
let root = if let Some(root) = root {
|
||||||
|
root
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let path = exercise
|
||||||
|
.path
|
||||||
|
.strip_prefix(root)
|
||||||
|
.expect("Invalid path outside of root")
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let target = Target {
|
||||||
|
name: exercise.name.clone(),
|
||||||
|
path,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (bins, lib) = if exercise.is_binary() {
|
||||||
|
(Some(vec![target]), None)
|
||||||
|
} else {
|
||||||
|
(None, Some(target))
|
||||||
|
};
|
||||||
|
|
||||||
|
let manifest = Manifest {
|
||||||
|
package: Package {
|
||||||
|
name: "exercises",
|
||||||
|
version: "0.1.0",
|
||||||
|
authors: &["The Rustlings Maintainers"],
|
||||||
|
edition: "2018",
|
||||||
|
publish: false,
|
||||||
|
},
|
||||||
|
bin: bins,
|
||||||
|
lib,
|
||||||
|
};
|
||||||
|
|
||||||
|
let source = format!(
|
||||||
|
"# This Cargo.toml is AUTOGENERATED, you shouldn't need to edit it.\n\
|
||||||
|
# The `rustling` commands do not rely on this manifest at all, its only purpose\n\
|
||||||
|
# is to help editors analyze your code.\n\
|
||||||
|
{}",
|
||||||
|
toml::ser::to_string_pretty(&manifest).expect("Invalid toml")
|
||||||
|
);
|
||||||
|
|
||||||
|
fs::write(format!("{}/Cargo.toml", root), source).expect("Unable to update manifest")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct Manifest {
|
||||||
|
package: Package,
|
||||||
|
bin: Option<Vec<Target>>,
|
||||||
|
lib: Option<Target>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct Package {
|
||||||
|
name: &'static str,
|
||||||
|
version: &'static str,
|
||||||
|
edition: &'static str,
|
||||||
|
authors: &'static [&'static str],
|
||||||
|
publish: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct Target {
|
||||||
|
name: String,
|
||||||
|
path: String,
|
||||||
|
}
|
|
@ -1,4 +1,7 @@
|
||||||
use crate::exercise::{CompiledExercise, Exercise, Mode, State};
|
use crate::{
|
||||||
|
exercise::{CompiledExercise, Exercise, Mode, State},
|
||||||
|
manifest,
|
||||||
|
};
|
||||||
use console::style;
|
use console::style;
|
||||||
use indicatif::ProgressBar;
|
use indicatif::ProgressBar;
|
||||||
|
|
||||||
|
@ -9,6 +12,7 @@ use indicatif::ProgressBar;
|
||||||
// determines whether or not the test harness outputs are displayed.
|
// determines whether or not the test harness outputs are displayed.
|
||||||
pub fn verify<'a>(
|
pub fn verify<'a>(
|
||||||
start_at: impl IntoIterator<Item = &'a Exercise>,
|
start_at: impl IntoIterator<Item = &'a Exercise>,
|
||||||
|
root: Option<&str>,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
) -> Result<(), &'a Exercise> {
|
) -> Result<(), &'a Exercise> {
|
||||||
for exercise in start_at {
|
for exercise in start_at {
|
||||||
|
@ -18,6 +22,7 @@ pub fn verify<'a>(
|
||||||
Mode::Clippy => compile_only(&exercise),
|
Mode::Clippy => compile_only(&exercise),
|
||||||
};
|
};
|
||||||
if !compile_result.unwrap_or(false) {
|
if !compile_result.unwrap_or(false) {
|
||||||
|
manifest::update(exercise, root);
|
||||||
return Err(exercise);
|
return Err(exercise);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use glob::glob;
|
use glob::glob;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn all_exercises_require_confirmation() {
|
fn all_exercises_require_confirmation() {
|
||||||
|
@ -13,16 +13,6 @@ fn all_exercises_require_confirmation() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn cargo_toml_is_up_to_date() {
|
|
||||||
let exercises_manifest = fs::read_to_string("exercises/Cargo.toml").unwrap();
|
|
||||||
let generated_manifest = tooling::generate(&Path::new("exercises"));
|
|
||||||
assert_eq!(
|
|
||||||
generated_manifest, exercises_manifest,
|
|
||||||
"exercises/Cargo.toml is not up to date, run `cargo generate-manifest`"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn all_exercises() -> impl Iterator<Item = PathBuf> {
|
fn all_exercises() -> impl Iterator<Item = PathBuf> {
|
||||||
glob("exercises/**/*.rs")
|
glob("exercises/**/*.rs")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "tooling"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["The Rustlings Maintainers"]
|
|
||||||
edition = "2018"
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
glob = "0.3.0"
|
|
||||||
serde = { version = "1.0.112", feature = ["derive"] }
|
|
||||||
toml = "0.5.6"
|
|
|
@ -1,79 +0,0 @@
|
||||||
use glob::glob;
|
|
||||||
use serde::Serialize;
|
|
||||||
use std::path::Path;
|
|
||||||
use toml;
|
|
||||||
|
|
||||||
pub fn generate(root: &Path) -> String {
|
|
||||||
let mut bins = vec![];
|
|
||||||
|
|
||||||
let root_glob = {
|
|
||||||
let mut p = root.to_owned();
|
|
||||||
p.push("**");
|
|
||||||
p.push("*.rs");
|
|
||||||
p
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut exercise_paths: Vec<_> = glob(root_glob.to_str().unwrap())
|
|
||||||
.unwrap()
|
|
||||||
.collect::<Result<_, _>>()
|
|
||||||
.expect("Error traversing root");
|
|
||||||
|
|
||||||
exercise_paths.sort();
|
|
||||||
|
|
||||||
for path in &exercise_paths {
|
|
||||||
let name = path
|
|
||||||
.file_stem()
|
|
||||||
.expect("Malformed exercise path")
|
|
||||||
.to_str()
|
|
||||||
.expect("Invalid UTF8 in exercise path")
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let target_path = path.strip_prefix(root).unwrap();
|
|
||||||
|
|
||||||
bins.push(Bin {
|
|
||||||
name,
|
|
||||||
path: target_path.to_str().unwrap().to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let manifest = Manifest {
|
|
||||||
package: Package {
|
|
||||||
name: "exercises",
|
|
||||||
version: "0.1.0",
|
|
||||||
authors: &["The Rustlings Maintainers"],
|
|
||||||
edition: "2018",
|
|
||||||
publish: false,
|
|
||||||
},
|
|
||||||
bin: bins,
|
|
||||||
};
|
|
||||||
|
|
||||||
format!(
|
|
||||||
"# This Cargo.toml is AUTOGENERATED, you shouldn't need to edit it.\n\
|
|
||||||
# The `rustling` commands do not rely on this manifest at all, its only purpose\n\
|
|
||||||
# is to help editors analyze your code.\n\
|
|
||||||
# To regenerate it, run `cargo generate-manifest`.\n\
|
|
||||||
{}",
|
|
||||||
toml::ser::to_string_pretty(&manifest).expect("Invalid toml")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct Manifest {
|
|
||||||
package: Package,
|
|
||||||
bin: Vec<Bin>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct Package {
|
|
||||||
name: &'static str,
|
|
||||||
version: &'static str,
|
|
||||||
edition: &'static str,
|
|
||||||
authors: &'static [&'static str],
|
|
||||||
publish: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct Bin {
|
|
||||||
name: String,
|
|
||||||
path: String,
|
|
||||||
}
|
|
Loading…
Reference in a new issue