Document not found (404)
+This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ +diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..f1731109 --- /dev/null +++ b/.nojekyll @@ -0,0 +1 @@ +This file makes sure that Github Pages doesn't process mdBook's output. diff --git a/404.html b/404.html new file mode 100644 index 00000000..53e9537e --- /dev/null +++ b/404.html @@ -0,0 +1,229 @@ + + +
+ + +This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ +Redirecting to ../../../basis/list/struct.Stack.html...
+ + + \ No newline at end of file diff --git a/doc/basis/list/struct.Stack.html b/doc/basis/list/struct.Stack.html new file mode 100644 index 00000000..7384a8df --- /dev/null +++ b/doc/basis/list/struct.Stack.html @@ -0,0 +1,15 @@ +pub struct Stack<T> { /* private fields */ }
pub fn hello() -> &'static str
pub(crate) enum Subcommands {
+ Verify(VerifyArgs),
+ Watch(WatchArgs),
+ Run(RunArgs),
+ Hint(HintArgs),
+ List(ListArgs),
+}
self
and other
values to be equal, and is used
+by ==
.pub(crate) enum WatchStatus {
+ Finished,
+ Unfinished,
+}
const CLIPPY_CARGO_TOML_PATH: &str = "./exercises/clippy/Cargo.toml";
const I_AM_DONE_REGEX: &str = r"(?m)^\s*///?\s*I\s+AM\s+NOT\s+DONE";
const RUSTC_COLOR_ARGS: &[&str];
pub enum Mode {
+ Compile,
+ Test,
+ Clippy,
+}
pub enum State {
+ Done,
+ Pending(Vec<ContextLine>),
+}
key
and return true
if they are equal.pub struct CompiledExercise<'a> {
+ exercise: &'a Exercise,
+ _handle: FileHandle,
+}
exercise: &'a Exercise
§_handle: FileHandle
pub struct ContextLine {
+ pub line: String,
+ pub number: usize,
+ pub important: bool,
+}
line: String
§number: usize
§important: bool
self
and other
values to be equal, and is used
+by ==
.key
and return true
if they are equal.pub struct Exercise {
+ pub name: String,
+ pub path: PathBuf,
+ pub mode: Mode,
+ pub hint: String,
+}
name: String
§path: PathBuf
§mode: Mode
§hint: String
pub struct ExerciseList {
+ pub exercises: Vec<Exercise>,
+}
exercises: Vec<Exercise>
pub struct ExerciseOutput {
+ pub stdout: String,
+ pub stderr: String,
+}
stdout: String
§stderr: String
struct FileHandle;
pub(crate) fn rustc_exists() -> bool
pub(crate) fn spawn_watch_shell(
+ failed_exercise_hint: &Arc<Mutex<Option<String>>>,
+ should_quit: Arc<AtomicBool>,
+)
verify
when files were editedpub(crate) struct Args {
+ pub(crate) nocapture: bool,
+ pub(crate) version: bool,
+ pub(crate) nested: Option<Subcommands>,
+}
Rustlings is a collection of small exercises to get you used to writing and reading Rust code
+nocapture: bool
show outputs from the test exercises
+version: bool
show the executable version
+nested: Option<Subcommands>
pub(crate) struct HintArgs {
+ pub(crate) name: String,
+}
Returns a hint for the given exercise
+name: String
the name of the exercise
+pub(crate) struct ListArgs {
+ pub(crate) paths: bool,
+ pub(crate) names: bool,
+ pub(crate) filter: Option<String>,
+ pub(crate) unsolved: bool,
+ pub(crate) solved: bool,
+}
Lists the exercises available in Rustlings
+paths: bool
show only the paths of the exercises
+names: bool
show only the names of the exercises
+filter: Option<String>
provide a string to match exercise names +comma separated patterns are acceptable
+unsolved: bool
display only exercises not yet solved
+solved: bool
display only exercises that have been solved
+pub(crate) struct RunArgs {
+ pub(crate) name: String,
+}
Runs/Tests a single exercise
+name: String
the name of the exercise
+pub(crate) struct VerifyArgs {}
Verifies all exercises according to the recommended order
+self
and other
values to be equal, and is used
+by ==
.pub(crate) struct WatchArgs {}
Reruns verify
when files were edited
Redirecting to macro.success.html...
+ + + \ No newline at end of file diff --git a/doc/rustlings/ui/macro.success.html b/doc/rustlings/ui/macro.success.html new file mode 100644 index 00000000..4fa7136e --- /dev/null +++ b/doc/rustlings/ui/macro.success.html @@ -0,0 +1,3 @@ +Redirecting to macro.warn.html...
+ + + \ No newline at end of file diff --git a/doc/rustlings/ui/macro.warn.html b/doc/rustlings/ui/macro.warn.html new file mode 100644 index 00000000..8078ed79 --- /dev/null +++ b/doc/rustlings/ui/macro.warn.html @@ -0,0 +1,3 @@ +enum RunMode {
+ Interactive,
+ NonInteractive,
+}
U::from(self)
.")
\ No newline at end of file
diff --git a/doc/search.desc/hello_world/hello_world-desc-0-.js b/doc/search.desc/hello_world/hello_world-desc-0-.js
new file mode 100644
index 00000000..a13d6ed4
--- /dev/null
+++ b/doc/search.desc/hello_world/hello_world-desc-0-.js
@@ -0,0 +1 @@
+searchState.loadedDescShard("hello_world", 0, "")
\ No newline at end of file
diff --git a/doc/search.desc/rustlings/rustlings-desc-0-.js b/doc/search.desc/rustlings/rustlings-desc-0-.js
new file mode 100644
index 00000000..e383ff3a
--- /dev/null
+++ b/doc/search.desc/rustlings/rustlings-desc-0-.js
@@ -0,0 +1 @@
+searchState.loadedDescShard("rustlings", 0, "Rustlings is a collection of small exercises to get you …\nReturns a hint for the given exercise\nLists the exercises available in Rustlings\nRuns/Tests a single exercise\nVerifies all exercises according to the recommended order\nReruns verify
when files were edited\nprovide a string to match exercise names comma separated …\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nthe name of the exercise\nthe name of the exercise\nshow only the names of the exercises\nshow outputs from the test exercises\nshow only the paths of the exercises\ndisplay only exercises that have been solved\ndisplay only exercises not yet solved\nshow the executable version\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nReturns the argument unchanged.\nCalls U::from(self)
.")
\ No newline at end of file
diff --git a/doc/settings.html b/doc/settings.html
new file mode 100644
index 00000000..bff663df
--- /dev/null
+++ b/doc/settings.html
@@ -0,0 +1 @@
+1 +
pub mod list;
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +
pub struct Stack<T> {
+ head: Link<T>,
+}
+
+type Link<T> = Option<Box<Node<T>>>;
+
+struct Node<T> {
+ elem: T,
+ next: Link<T>,
+}
+
+impl<T> Stack<T> {
+ pub fn new() -> Self {
+ Stack { head: None }
+ }
+
+ pub fn push(&mut self, elem: T) {
+ let new_node = Box::new(Node {
+ elem,
+ next: self.head.take(),
+ });
+
+ self.head = Some(new_node);
+ }
+
+ pub fn pop(&mut self) -> Option<T> {
+ self.head.take().map(|node| {
+ self.head = node.next;
+ node.elem
+ })
+ }
+
+ pub fn peek(&self) -> Option<&T> {
+ self.head.as_ref().map(|node| &node.elem)
+ }
+
+ pub fn peek_mut(&mut self) -> Option<&mut T> {
+ self.head.as_mut().map(|node| &mut node.elem)
+ }
+}
+
+impl<T> Default for Stack<T> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl<T> Drop for Stack<T> {
+ fn drop(&mut self) {
+ let mut cur_link = self.head.take();
+ while let Some(mut boxed_node) = cur_link {
+ cur_link = boxed_node.next.take();
+ }
+ }
+}
+
+pub struct IntoIter<T>(Stack<T>);
+
+impl<T> IntoIterator for Stack<T> {
+ type Item = T;
+ type IntoIter = IntoIter<T>;
+ fn into_iter(self) -> Self::IntoIter {
+ IntoIter(self)
+ }
+}
+
+impl<T> Iterator for IntoIter<T> {
+ type Item = T;
+ fn next(&mut self) -> Option<Self::Item> {
+ self.0.pop()
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::Stack;
+
+ #[test]
+ fn push_and_pop() {
+ let mut list = Stack::new();
+ assert_eq!(list.pop(), None);
+
+ list.push(1);
+ list.push(2);
+ list.push(3);
+ assert_eq!(list.pop(), Some(3));
+ assert_eq!(list.pop(), Some(2));
+
+ list.push(4);
+ list.push(5);
+ assert_eq!(list.pop(), Some(5));
+ assert_eq!(list.pop(), Some(4));
+ assert_eq!(list.pop(), Some(1));
+ assert_eq!(list.pop(), None);
+ }
+
+ #[test]
+ fn peek() {
+ let mut list = Stack::new();
+ assert_eq!(list.peek(), None);
+ assert_eq!(list.peek_mut(), None);
+
+ list.push(1);
+ list.push(2);
+ list.push(3);
+ assert_eq!(list.peek(), Some(&3));
+ assert_eq!(list.peek_mut(), Some(&mut 3));
+
+ if let Some(value) = list.peek_mut() {
+ *value = 42;
+ }
+ assert_eq!(list.peek(), Some(&42));
+ assert_eq!(list.pop(), Some(42));
+ }
+
+ #[test]
+ fn into_iter() {
+ let mut list = Stack::new();
+ list.push(1);
+ list.push(2);
+ list.push(3);
+
+ let mut iter = list.into_iter();
+ assert_eq!(iter.next(), Some(3));
+ assert_eq!(iter.next(), Some(2));
+ assert_eq!(iter.next(), Some(1));
+ assert_eq!(iter.next(), None);
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +
use regex::Regex;
+use serde::Deserialize;
+use std::env;
+use std::fmt::{self, Display, Formatter};
+use std::fs::{self, remove_file, File};
+use std::io::Read;
+use std::path::PathBuf;
+use std::process::{self, Command};
+
+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 CONTEXT: usize = 2;
+const CLIPPY_CARGO_TOML_PATH: &str = "./exercises/clippy/Cargo.toml";
+
+// Get a temporary file name that is hopefully unique
+#[inline]
+fn temp_file() -> String {
+ let thread_id: String = format!("{:?}", std::thread::current().id())
+ .chars()
+ .filter(|c| c.is_alphanumeric())
+ .collect();
+
+ format!("./temp_{}_{}", process::id(), thread_id)
+}
+
+// The mode of the exercise.
+#[derive(Deserialize, Copy, Clone, Debug)]
+#[serde(rename_all = "lowercase")]
+pub enum Mode {
+ // Indicates that the exercise should be compiled as a binary
+ Compile,
+ // Indicates that the exercise should be compiled as a test harness
+ Test,
+ // Indicates that the exercise should be linted with clippy
+ Clippy,
+}
+
+#[derive(Deserialize)]
+pub struct ExerciseList {
+ pub exercises: Vec<Exercise>,
+}
+
+// A representation of a rustlings exercise.
+// This is deserialized from the accompanying info.toml file
+#[derive(Deserialize, Debug)]
+pub struct Exercise {
+ // Name of the exercise
+ pub name: String,
+ // The path to the file containing the exercise's source code
+ pub path: PathBuf,
+ // The mode of the exercise (Test, Compile, or Clippy)
+ pub mode: Mode,
+ // The hint text associated with the exercise
+ pub hint: String,
+}
+
+// An enum to track of the state of an Exercise.
+// An Exercise can be either Done or Pending
+#[derive(PartialEq, Eq, Debug)]
+pub enum State {
+ // The state of the exercise once it's been completed
+ Done,
+ // The state of the exercise while it's not completed yet
+ Pending(Vec<ContextLine>),
+}
+
+// The context information of a pending exercise
+#[derive(PartialEq, Eq, Debug)]
+pub struct ContextLine {
+ // The source code that is still pending completion
+ pub line: String,
+ // The line number of the source code still pending completion
+ pub number: usize,
+ // Whether or not this is important
+ pub important: bool,
+}
+
+// The result of compiling an exercise
+pub struct CompiledExercise<'a> {
+ exercise: &'a Exercise,
+ _handle: FileHandle,
+}
+
+impl<'a> CompiledExercise<'a> {
+ // Run the compiled exercise
+ pub fn run(&self) -> Result<ExerciseOutput, ExerciseOutput> {
+ self.exercise.run()
+ }
+}
+
+// A representation of an already executed binary
+#[derive(Debug)]
+pub struct ExerciseOutput {
+ // The textual contents of the standard output of the binary
+ pub stdout: String,
+ // The textual contents of the standard error of the binary
+ pub stderr: String,
+}
+
+struct FileHandle;
+
+impl Drop for FileHandle {
+ fn drop(&mut self) {
+ clean();
+ }
+}
+
+impl Exercise {
+ pub fn compile(&self) -> Result<CompiledExercise, ExerciseOutput> {
+ let cmd = match self.mode {
+ Mode::Compile => Command::new("rustc")
+ .args([self.path.to_str().unwrap(), "-o", &temp_file()])
+ .args(RUSTC_COLOR_ARGS)
+ .output(),
+ Mode::Test => Command::new("rustc")
+ .args(["--test", self.path.to_str().unwrap(), "-o", &temp_file()])
+ .args(RUSTC_COLOR_ARGS)
+ .output(),
+ Mode::Clippy => {
+ let cargo_toml = format!(
+ r#"[package]
+name = "{}"
+version = "0.0.1"
+edition = "2018"
+[[bin]]
+name = "{}"
+path = "{}.rs""#,
+ self.name, self.name, self.name
+ );
+ let cargo_toml_error_msg = if env::var("NO_EMOJI").is_ok() {
+ "Failed to write Clippy Cargo.toml file."
+ } else {
+ "Failed to write 📎 Clippy 📎 Cargo.toml file."
+ };
+ fs::write(CLIPPY_CARGO_TOML_PATH, cargo_toml).expect(cargo_toml_error_msg);
+ // To support the ability to run the clippy exercises, build
+ // an executable, in addition to running clippy. With a
+ // compilation failure, this would silently fail. But we expect
+ // clippy to reflect the same failure while compiling later.
+ Command::new("rustc")
+ .args([self.path.to_str().unwrap(), "-o", &temp_file()])
+ .args(RUSTC_COLOR_ARGS)
+ .output()
+ .expect("Failed to compile!");
+ // Due to an issue with Clippy, a cargo clean is required to catch all lints.
+ // See https://github.com/rust-lang/rust-clippy/issues/2604
+ // This is already fixed on Clippy's master branch. See this issue to track merging into Cargo:
+ // https://github.com/rust-lang/rust-clippy/issues/3837
+ Command::new("cargo")
+ .args(["clean", "--manifest-path", CLIPPY_CARGO_TOML_PATH])
+ .args(RUSTC_COLOR_ARGS)
+ .output()
+ .expect("Failed to run 'cargo clean'");
+ Command::new("cargo")
+ .args(["clippy", "--manifest-path", CLIPPY_CARGO_TOML_PATH])
+ .args(RUSTC_COLOR_ARGS)
+ .args(["--", "-D", "warnings", "-D", "clippy::float_cmp"])
+ .output()
+ }
+ }
+ .expect("Failed to run 'compile' command.");
+
+ if cmd.status.success() {
+ Ok(CompiledExercise {
+ exercise: self,
+ _handle: FileHandle,
+ })
+ } else {
+ clean();
+ Err(ExerciseOutput {
+ stdout: String::from_utf8_lossy(&cmd.stdout).to_string(),
+ stderr: String::from_utf8_lossy(&cmd.stderr).to_string(),
+ })
+ }
+ }
+
+ fn run(&self) -> Result<ExerciseOutput, ExerciseOutput> {
+ let arg = match self.mode {
+ Mode::Test => "--show-output",
+ _ => "",
+ };
+ let cmd = Command::new(temp_file())
+ .arg(arg)
+ .output()
+ .expect("Failed to run 'run' command");
+
+ let output = ExerciseOutput {
+ stdout: String::from_utf8_lossy(&cmd.stdout).to_string(),
+ stderr: String::from_utf8_lossy(&cmd.stderr).to_string(),
+ };
+
+ if cmd.status.success() {
+ Ok(output)
+ } else {
+ Err(output)
+ }
+ }
+
+ pub fn state(&self) -> State {
+ let mut source_file =
+ 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();
+
+ if !re.is_match(&source) {
+ return State::Done;
+ }
+
+ let matched_line_index = source
+ .lines()
+ .enumerate()
+ .find_map(|(i, line)| if re.is_match(line) { Some(i) } else { None })
+ .expect("This should not happen at all");
+
+ let min_line = ((matched_line_index as i32) - (CONTEXT as i32)).max(0) as usize;
+ let max_line = matched_line_index + CONTEXT;
+
+ let context = source
+ .lines()
+ .enumerate()
+ .filter(|&(i, _)| i >= min_line && i <= max_line)
+ .map(|(i, line)| ContextLine {
+ line: line.to_string(),
+ number: i + 1,
+ important: i == matched_line_index,
+ })
+ .collect();
+
+ State::Pending(context)
+ }
+
+ // Check that the exercise looks to be solved using self.state()
+ // This is not the best way to check since
+ // the user can just remove the "I AM NOT DONE" string from the file
+ // without actually having solved anything.
+ // The only other way to truly check this would to compile and run
+ // the exercise; which would be both costly and counterintuitive
+ pub fn looks_done(&self) -> bool {
+ self.state() == State::Done
+ }
+}
+
+impl Display for Exercise {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "{}", self.path.to_str().unwrap())
+ }
+}
+
+#[inline]
+fn clean() {
+ let _ignored = remove_file(temp_file());
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use std::path::Path;
+
+ #[test]
+ fn test_clean() {
+ File::create(temp_file()).unwrap();
+ let exercise = Exercise {
+ name: String::from("example"),
+ path: PathBuf::from("tests/fixture/state/pending_exercise.rs"),
+ mode: Mode::Compile,
+ hint: String::from(""),
+ };
+ let compiled = exercise.compile().unwrap();
+ drop(compiled);
+ assert!(!Path::new(&temp_file()).exists());
+ }
+
+ #[test]
+ fn test_pending_state() {
+ let exercise = Exercise {
+ name: "pending_exercise".into(),
+ path: PathBuf::from("tests/fixture/state/pending_exercise.rs"),
+ mode: Mode::Compile,
+ hint: String::new(),
+ };
+
+ let state = exercise.state();
+ let expected = vec![
+ ContextLine {
+ line: "// fake_exercise".to_string(),
+ number: 1,
+ important: false,
+ },
+ ContextLine {
+ line: "".to_string(),
+ number: 2,
+ important: false,
+ },
+ ContextLine {
+ line: "// I AM NOT DONE".to_string(),
+ number: 3,
+ important: true,
+ },
+ ContextLine {
+ line: "".to_string(),
+ number: 4,
+ important: false,
+ },
+ ContextLine {
+ line: "fn main() {".to_string(),
+ number: 5,
+ important: false,
+ },
+ ];
+
+ assert_eq!(state, State::Pending(expected));
+ }
+
+ #[test]
+ fn test_finished_exercise() {
+ let exercise = Exercise {
+ name: "finished_exercise".into(),
+ path: PathBuf::from("tests/fixture/state/finished_exercise.rs"),
+ mode: Mode::Compile,
+ hint: String::new(),
+ };
+
+ assert_eq!(exercise.state(), State::Done);
+ }
+
+ #[test]
+ fn test_exercise_with_output() {
+ let exercise = Exercise {
+ name: "exercise_with_output".into(),
+ path: PathBuf::from("tests/fixture/success/testSuccess.rs"),
+ mode: Mode::Test,
+ hint: String::new(),
+ };
+ let out = exercise.compile().unwrap().run().unwrap();
+ assert!(out.stdout.contains("THIS TEST TOO SHALL PASS"));
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +
use crate::exercise::{Exercise, ExerciseList};
+use crate::run::run;
+use crate::verify::verify;
+use argh::FromArgs;
+use console::Emoji;
+use notify::DebouncedEvent;
+use notify::{RecommendedWatcher, RecursiveMode, Watcher};
+use std::ffi::OsStr;
+use std::fs;
+use std::io::{self, prelude::*};
+use std::path::Path;
+use std::process::{Command, Stdio};
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::mpsc::{channel, RecvTimeoutError};
+use std::sync::{Arc, Mutex};
+use std::thread;
+use std::time::Duration;
+
+#[macro_use]
+mod ui;
+
+mod exercise;
+mod run;
+mod verify;
+
+// In sync with crate version
+const VERSION: &str = "4.6.0";
+
+#[derive(FromArgs, PartialEq, Debug)]
+/// Rustlings is a collection of small exercises to get you used to writing and reading Rust code
+struct Args {
+ /// show outputs from the test exercises
+ #[argh(switch)]
+ nocapture: bool,
+ /// show the executable version
+ #[argh(switch, short = 'v')]
+ version: bool,
+ #[argh(subcommand)]
+ nested: Option<Subcommands>,
+}
+
+#[derive(FromArgs, PartialEq, Debug)]
+#[argh(subcommand)]
+enum Subcommands {
+ Verify(VerifyArgs),
+ Watch(WatchArgs),
+ Run(RunArgs),
+ Hint(HintArgs),
+ List(ListArgs),
+}
+
+#[derive(FromArgs, PartialEq, Debug)]
+#[argh(subcommand, name = "verify")]
+/// Verifies all exercises according to the recommended order
+struct VerifyArgs {}
+
+#[derive(FromArgs, PartialEq, Debug)]
+#[argh(subcommand, name = "watch")]
+/// Reruns `verify` when files were edited
+struct WatchArgs {}
+
+#[derive(FromArgs, PartialEq, Debug)]
+#[argh(subcommand, name = "run")]
+/// Runs/Tests a single exercise
+struct RunArgs {
+ #[argh(positional)]
+ /// the name of the exercise
+ name: String,
+}
+
+#[derive(FromArgs, PartialEq, Debug)]
+#[argh(subcommand, name = "hint")]
+/// Returns a hint for the given exercise
+struct HintArgs {
+ #[argh(positional)]
+ /// the name of the exercise
+ name: String,
+}
+
+#[derive(FromArgs, PartialEq, Debug)]
+#[argh(subcommand, name = "list")]
+/// Lists the exercises available in Rustlings
+struct ListArgs {
+ #[argh(switch, short = 'p')]
+ /// show only the paths of the exercises
+ paths: bool,
+ #[argh(switch, short = 'n')]
+ /// show only the names of the exercises
+ names: bool,
+ #[argh(option, short = 'f')]
+ /// provide a string to match exercise names
+ /// comma separated patterns are acceptable
+ filter: Option<String>,
+ #[argh(switch, short = 'u')]
+ /// display only exercises not yet solved
+ unsolved: bool,
+ #[argh(switch, short = 's')]
+ /// display only exercises that have been solved
+ solved: bool,
+}
+
+fn main() {
+ let args: Args = argh::from_env();
+
+ if args.version {
+ println!("v{VERSION}");
+ std::process::exit(0);
+ }
+
+ if args.nested.is_none() {
+ println!();
+ println!(r#" welcome to... "#);
+ println!(r#" _ _ _ "#);
+ println!(r#" _ __ _ _ ___| |_| (_)_ __ __ _ ___ "#);
+ println!(r#" | '__| | | / __| __| | | '_ \ / _` / __| "#);
+ println!(r#" | | | |_| \__ \ |_| | | | | | (_| \__ \ "#);
+ println!(r#" |_| \__,_|___/\__|_|_|_| |_|\__, |___/ "#);
+ println!(r#" |___/ "#);
+ println!();
+ }
+
+ if !Path::new("info.toml").exists() {
+ println!(
+ "{} must be run from the rustlings directory",
+ std::env::current_exe().unwrap().to_str().unwrap()
+ );
+ println!("Try `cd rustlings/`!");
+ std::process::exit(1);
+ }
+
+ if !rustc_exists() {
+ println!("We cannot find `rustc`.");
+ println!("Try running `rustc --version` to diagnose your problem.");
+ println!("For instructions on how to install Rust, check the README.");
+ std::process::exit(1);
+ }
+
+ let toml_str = &fs::read_to_string("info.toml").unwrap();
+ let exercises = toml::from_str::<ExerciseList>(toml_str).unwrap().exercises;
+ let verbose = args.nocapture;
+
+ let command = args.nested.unwrap_or_else(|| {
+ let text = "Thanks for installing Rustlings!
+
+Is this your first time? Don't worry, Rustlings was made for beginners! We are
+going to teach you a lot of things about Rust, but before we can get
+started, here's a couple of notes about how Rustlings operates:
+
+1. The central concept behind Rustlings is that you solve exercises. These
+ exercises usually have some sort of syntax error in them, which will cause
+ them to fail compilation or testing. Sometimes there's a logic error instead
+ of a syntax error. No matter what error, it's your job to find it and fix it!
+ You'll know when you fixed it because then, the exercise will compile and
+ Rustlings will be able to move on to the next exercise.
+2. If you run Rustlings in watch mode (which we recommend), it'll automatically
+ start with the first exercise. Don't get confused by an error message popping
+ up as soon as you run Rustlings! This is part of the exercise that you're
+ supposed to solve, so open the exercise file in an editor and start your
+ detective work!
+3. If you're stuck on an exercise, there is a helpful hint you can view by typing
+ 'hint' (in watch mode), or running `rustlings hint myexercise`.
+4. If an exercise doesn't make sense to you, feel free to open an issue on GitHub!
+ (https://github.com/rust-lang/rustlings/issues/new). We look at every issue,
+ and sometimes, other learners do too so you can help each other out!
+
+Got all that? Great! To get started, run `rustlings watch` in order to get the first
+exercise. Make sure to have your editor open!
+";
+ println!("{text}");
+ std::process::exit(0);
+ });
+ match command {
+ Subcommands::List(subargs) => {
+ if !subargs.paths && !subargs.names {
+ println!("{:<17}\t{:<46}\t{:<7}", "Name", "Path", "Status");
+ }
+ let mut exercises_done: u16 = 0;
+ let filters = subargs.filter.clone().unwrap_or_default().to_lowercase();
+ exercises.iter().for_each(|e| {
+ let fname = format!("{}", e.path.display());
+ let filter_cond = filters
+ .split(',')
+ .filter(|f| !f.trim().is_empty())
+ .any(|f| e.name.contains(f) || fname.contains(f));
+ let status = if e.looks_done() {
+ exercises_done += 1;
+ "Done"
+ } else {
+ "Pending"
+ };
+ let solve_cond = {
+ (e.looks_done() && subargs.solved)
+ || (!e.looks_done() && subargs.unsolved)
+ || (!subargs.solved && !subargs.unsolved)
+ };
+ if solve_cond && (filter_cond || subargs.filter.is_none()) {
+ let line = if subargs.paths {
+ format!("{fname}\n")
+ } else if subargs.names {
+ format!("{}\n", e.name)
+ } else {
+ format!("{:<17}\t{:<46}\t{:<7}\n", e.name, fname, status)
+ };
+ // Somehow using println! leads to the binary panicking
+ // when its output is piped.
+ // So, we're handling a Broken Pipe error and exiting with 0 anyway
+ let stdout = std::io::stdout();
+ {
+ let mut handle = stdout.lock();
+ handle.write_all(line.as_bytes()).unwrap_or_else(|e| {
+ match e.kind() {
+ std::io::ErrorKind::BrokenPipe => std::process::exit(0),
+ _ => std::process::exit(1),
+ };
+ });
+ }
+ }
+ });
+ let percentage_progress = exercises_done as f32 / exercises.len() as f32 * 100.0;
+ println!(
+ "Progress: You completed {} / {} exercises ({:.2} %).",
+ exercises_done,
+ exercises.len(),
+ percentage_progress
+ );
+ std::process::exit(0);
+ }
+
+ Subcommands::Run(subargs) => {
+ let exercise = find_exercise(&subargs.name, &exercises);
+
+ run(exercise, verbose).unwrap_or_else(|_| std::process::exit(1));
+ }
+
+ Subcommands::Hint(subargs) => {
+ let exercise = find_exercise(&subargs.name, &exercises);
+
+ println!("{}", exercise.hint);
+ }
+
+ Subcommands::Verify(_subargs) => {
+ verify(&exercises, verbose).unwrap_or_else(|_| std::process::exit(1));
+ }
+
+ Subcommands::Watch(_subargs) => match watch(&exercises, verbose) {
+ Err(e) => {
+ println!("Error: Could not watch your progress. Error message was {e:?}.");
+ println!("Most likely you've run out of disk space or your 'inotify limit' has been reached.");
+ std::process::exit(1);
+ }
+ Ok(WatchStatus::Finished) => {
+ println!(
+ "{emoji} All exercises completed! {emoji}",
+ emoji = Emoji("🎉", "★")
+ );
+ println!();
+ println!("+----------------------------------------------------+");
+ println!("| You made it to the Fe-nish line! |");
+ println!("+-------------------------- ------------------------+");
+ println!(" \\/ ");
+ println!(" ▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒ ");
+ println!(" ▒▒▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒▒▒ ");
+ println!(" ▒▒▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒▒▒ ");
+ println!(" ░░▒▒▒▒░░▒▒ ▒▒ ▒▒ ▒▒ ▒▒░░▒▒▒▒ ");
+ println!(" ▓▓▓▓▓▓▓▓ ▓▓ ▓▓██ ▓▓ ▓▓██ ▓▓ ▓▓▓▓▓▓▓▓ ");
+ println!(" ▒▒▒▒ ▒▒ ████ ▒▒ ████ ▒▒░░ ▒▒▒▒ ");
+ println!(" ▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒ ");
+ println!(" ▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▒▒▒▒▒▒▒▒▓▓▒▒▓▓▒▒▒▒▒▒▒▒ ");
+ println!(" ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ");
+ println!(" ▒▒▒▒▒▒▒▒▒▒██▒▒▒▒▒▒██▒▒▒▒▒▒▒▒▒▒ ");
+ println!(" ▒▒ ▒▒▒▒▒▒▒▒▒▒██████▒▒▒▒▒▒▒▒▒▒ ▒▒ ");
+ println!(" ▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒ ");
+ println!(" ▒▒ ▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒ ▒▒ ");
+ println!(" ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ");
+ println!(" ▒▒ ▒▒ ▒▒ ▒▒ ");
+ println!();
+ println!("We hope you enjoyed learning about the various aspects of Rust!");
+ println!(
+ "If you noticed any issues, please don't hesitate to report them to our repo."
+ );
+ println!(
+ "You can also contribute your own exercises to help the greater community!"
+ );
+ println!();
+ println!("Before reporting an issue or contributing, please read our guidelines:");
+ println!("https://github.com/rust-lang/rustlings/blob/main/CONTRIBUTING.md");
+ }
+ Ok(WatchStatus::Unfinished) => {
+ println!("We hope you're enjoying learning about Rust!");
+ println!("If you want to continue working on the exercises at a later point, you can simply run `rustlings watch` again");
+ }
+ },
+ }
+}
+
+fn spawn_watch_shell(
+ failed_exercise_hint: &Arc<Mutex<Option<String>>>,
+ should_quit: Arc<AtomicBool>,
+) {
+ let failed_exercise_hint = Arc::clone(failed_exercise_hint);
+ println!("Welcome to watch mode! You can type 'help' to get an overview of the commands you can use here.");
+ thread::spawn(move || loop {
+ let mut input = String::new();
+ match io::stdin().read_line(&mut input) {
+ Ok(_) => {
+ let input = input.trim();
+ if input == "hint" {
+ if let Some(hint) = &*failed_exercise_hint.lock().unwrap() {
+ println!("{hint}");
+ }
+ } else if input == "clear" {
+ println!("\x1B[2J\x1B[1;1H");
+ } else if input.eq("quit") {
+ should_quit.store(true, Ordering::SeqCst);
+ println!("Bye!");
+ } else if input.eq("help") {
+ println!("Commands available to you in watch mode:");
+ println!(" hint - prints the current exercise's hint");
+ println!(" clear - clears the screen");
+ println!(" quit - quits watch mode");
+ println!(" help - displays this help message");
+ println!();
+ println!("Watch mode automatically re-evaluates the current exercise");
+ println!("when you edit a file's contents.")
+ } else {
+ println!("unknown command: {input}");
+ }
+ }
+ Err(error) => println!("error reading command: {error}"),
+ }
+ });
+}
+
+fn find_exercise<'a>(name: &str, exercises: &'a [Exercise]) -> &'a Exercise {
+ if name.eq("next") {
+ exercises
+ .iter()
+ .find(|e| !e.looks_done())
+ .unwrap_or_else(|| {
+ println!("🎉 Congratulations! You have done all the exercises!");
+ println!("🔚 There are no more exercises to do next!");
+ std::process::exit(1)
+ })
+ } else {
+ exercises
+ .iter()
+ .find(|e| e.name == name)
+ .unwrap_or_else(|| {
+ println!("No exercise found for '{name}'!");
+ std::process::exit(1)
+ })
+ }
+}
+
+enum WatchStatus {
+ Finished,
+ Unfinished,
+}
+
+fn watch(exercises: &[Exercise], verbose: bool) -> notify::Result<WatchStatus> {
+ /* Clears the terminal with an ANSI escape code.
+ Works in UNIX and newer Windows terminals. */
+ fn clear_screen() {
+ println!("\x1Bc");
+ }
+
+ let (tx, rx) = channel();
+ let should_quit = Arc::new(AtomicBool::new(false));
+
+ let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(2))?;
+ watcher.watch(Path::new("./exercises"), RecursiveMode::Recursive)?;
+
+ clear_screen();
+
+ let to_owned_hint = |t: &Exercise| t.hint.to_owned();
+ let failed_exercise_hint = match verify(exercises.iter(), verbose) {
+ Ok(_) => return Ok(WatchStatus::Finished),
+ Err(exercise) => Arc::new(Mutex::new(Some(to_owned_hint(exercise)))),
+ };
+ spawn_watch_shell(&failed_exercise_hint, Arc::clone(&should_quit));
+ loop {
+ match rx.recv_timeout(Duration::from_secs(1)) {
+ Ok(event) => match event {
+ DebouncedEvent::Create(b) | DebouncedEvent::Chmod(b) | DebouncedEvent::Write(b) => {
+ if b.extension() == Some(OsStr::new("rs")) && b.exists() {
+ let filepath = b.as_path().canonicalize().unwrap();
+ let pending_exercises = exercises
+ .iter()
+ .skip_while(|e| !filepath.ends_with(&e.path))
+ // .filter(|e| filepath.ends_with(&e.path))
+ .chain(
+ exercises
+ .iter()
+ .filter(|e| !e.looks_done() && !filepath.ends_with(&e.path)),
+ );
+ clear_screen();
+ match verify(pending_exercises, verbose) {
+ Ok(_) => return Ok(WatchStatus::Finished),
+ Err(exercise) => {
+ let mut failed_exercise_hint = failed_exercise_hint.lock().unwrap();
+ *failed_exercise_hint = Some(to_owned_hint(exercise));
+ }
+ }
+ }
+ }
+ _ => {}
+ },
+ Err(RecvTimeoutError::Timeout) => {
+ // the timeout expired, just check the `should_quit` variable below then loop again
+ }
+ Err(e) => println!("watch error: {e:?}"),
+ }
+ // Check if we need to exit
+ if should_quit.load(Ordering::SeqCst) {
+ return Ok(WatchStatus::Unfinished);
+ }
+ }
+}
+
+fn rustc_exists() -> bool {
+ Command::new("rustc")
+ .args(["--version"])
+ .stdout(Stdio::null())
+ .spawn()
+ .and_then(|mut child| child.wait())
+ .map(|status| status.success())
+ .unwrap_or(false)
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +
use crate::exercise::{Exercise, Mode};
+use crate::verify::test;
+use indicatif::ProgressBar;
+use std::time::Duration;
+
+// Invoke the rust compiler on the path of the given exercise,
+// and run the ensuing binary.
+// The verbose argument helps determine whether or not to show
+// the output from the test harnesses (if the mode of the exercise is test)
+pub fn run(exercise: &Exercise, verbose: bool) -> Result<(), ()> {
+ match exercise.mode {
+ Mode::Test => test(exercise, verbose)?,
+ Mode::Compile => compile_and_run(exercise)?,
+ Mode::Clippy => compile_and_run(exercise)?,
+ }
+ Ok(())
+}
+
+// Invoke the rust compiler on the path of the given exercise
+// and run the ensuing binary.
+// This is strictly for non-test binaries, so output is displayed
+fn compile_and_run(exercise: &Exercise) -> Result<(), ()> {
+ let progress_bar = ProgressBar::new_spinner();
+ progress_bar.set_message(format!("Compiling {exercise}..."));
+ progress_bar.enable_steady_tick(Duration::from_millis(100));
+
+ let compilation_result = exercise.compile();
+ let compilation = match compilation_result {
+ Ok(compilation) => compilation,
+ Err(output) => {
+ progress_bar.finish_and_clear();
+ warn!(
+ "Compilation of {} failed!, Compiler error message:\n",
+ exercise
+ );
+ println!("{}", output.stderr);
+ return Err(());
+ }
+ };
+
+ progress_bar.set_message(format!("Running {exercise}..."));
+ let result = compilation.run();
+ progress_bar.finish_and_clear();
+
+ match result {
+ Ok(output) => {
+ println!("{}", output.stdout);
+ success!("Successfully ran {}", exercise);
+ Ok(())
+ }
+ Err(output) => {
+ println!("{}", output.stdout);
+ println!("{}", output.stderr);
+
+ warn!("Ran {} with errors", exercise);
+ Err(())
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +
macro_rules! warn {
+ ($fmt:literal, $ex:expr) => {{
+ use console::{style, Emoji};
+ use std::env;
+ let formatstr = format!($fmt, $ex);
+ if env::var("NO_EMOJI").is_ok() {
+ println!("{} {}", style("!").red(), style(formatstr).red());
+ } else {
+ println!(
+ "{} {}",
+ style(Emoji("⚠️ ", "!")).red(),
+ style(formatstr).red()
+ );
+ }
+ }};
+}
+
+macro_rules! success {
+ ($fmt:literal, $ex:expr) => {{
+ use console::{style, Emoji};
+ use std::env;
+ let formatstr = format!($fmt, $ex);
+ if env::var("NO_EMOJI").is_ok() {
+ println!("{} {}", style("✓").green(), style(formatstr).green());
+ } else {
+ println!(
+ "{} {}",
+ style(Emoji("✅", "✓")).green(),
+ style(formatstr).green()
+ );
+ }
+ }};
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +
use crate::exercise::{CompiledExercise, Exercise, Mode, State};
+use console::style;
+use indicatif::ProgressBar;
+use std::env;
+use std::time::Duration;
+
+// Verify that the provided container of Exercise objects
+// can be compiled and run without any failures.
+// Any such failures will be reported to the end user.
+// If the Exercise being verified is a test, the verbose boolean
+// determines whether or not the test harness outputs are displayed.
+pub fn verify<'a>(
+ start_at: impl IntoIterator<Item = &'a Exercise>,
+ verbose: bool,
+) -> Result<(), &'a Exercise> {
+ for exercise in start_at {
+ let compile_result = match exercise.mode {
+ Mode::Test => compile_and_test(exercise, RunMode::Interactive, verbose),
+ Mode::Compile => compile_and_run_interactively(exercise),
+ Mode::Clippy => compile_only(exercise),
+ };
+ if !compile_result.unwrap_or(false) {
+ return Err(exercise);
+ }
+ }
+ Ok(())
+}
+
+enum RunMode {
+ Interactive,
+ NonInteractive,
+}
+
+// Compile and run the resulting test harness of the given Exercise
+pub fn test(exercise: &Exercise, verbose: bool) -> Result<(), ()> {
+ compile_and_test(exercise, RunMode::NonInteractive, verbose)?;
+ Ok(())
+}
+
+// Invoke the rust compiler without running the resulting binary
+fn compile_only(exercise: &Exercise) -> Result<bool, ()> {
+ let progress_bar = ProgressBar::new_spinner();
+ progress_bar.set_message(format!("Compiling {exercise}..."));
+ progress_bar.enable_steady_tick(Duration::from_millis(100));
+
+ let _ = compile(exercise, &progress_bar)?;
+ progress_bar.finish_and_clear();
+
+ success!("Successfully compiled {}!", exercise);
+ Ok(prompt_for_completion(exercise, None))
+}
+
+// Compile the given Exercise and run the resulting binary in an interactive mode
+fn compile_and_run_interactively(exercise: &Exercise) -> Result<bool, ()> {
+ let progress_bar = ProgressBar::new_spinner();
+ progress_bar.set_message(format!("Compiling {exercise}..."));
+ progress_bar.enable_steady_tick(Duration::from_millis(100));
+
+ let compilation = compile(exercise, &progress_bar)?;
+
+ progress_bar.set_message(format!("Running {exercise}..."));
+ let result = compilation.run();
+ progress_bar.finish_and_clear();
+
+ let output = match result {
+ Ok(output) => output,
+ Err(output) => {
+ warn!("Ran {} with errors", exercise);
+ println!("{}", output.stdout);
+ println!("{}", output.stderr);
+ return Err(());
+ }
+ };
+
+ success!("Successfully ran {}!", exercise);
+
+ Ok(prompt_for_completion(exercise, Some(output.stdout)))
+}
+
+// Compile the given Exercise as a test harness and display
+// the output if verbose is set to true
+fn compile_and_test(exercise: &Exercise, run_mode: RunMode, verbose: bool) -> Result<bool, ()> {
+ let progress_bar = ProgressBar::new_spinner();
+ progress_bar.set_message(format!("Testing {exercise}..."));
+ progress_bar.enable_steady_tick(Duration::from_millis(100));
+
+ let compilation = compile(exercise, &progress_bar)?;
+ let result = compilation.run();
+ progress_bar.finish_and_clear();
+
+ match result {
+ Ok(output) => {
+ if verbose {
+ println!("{}", output.stdout);
+ }
+ success!("Successfully tested {}", &exercise);
+ if let RunMode::Interactive = run_mode {
+ Ok(prompt_for_completion(exercise, None))
+ } else {
+ Ok(true)
+ }
+ }
+ Err(output) => {
+ warn!(
+ "Testing of {} failed! Please try again. Here's the output:",
+ exercise
+ );
+ println!("{}", output.stdout);
+ Err(())
+ }
+ }
+}
+
+// Compile the given Exercise and return an object with information
+// about the state of the compilation
+fn compile<'a>(
+ exercise: &'a Exercise,
+ progress_bar: &ProgressBar,
+) -> Result<CompiledExercise<'a>, ()> {
+ let compilation_result = exercise.compile();
+
+ match compilation_result {
+ Ok(compilation) => Ok(compilation),
+ Err(output) => {
+ progress_bar.finish_and_clear();
+ warn!(
+ "Compiling of {} failed! Please try again. Here's the output:",
+ exercise
+ );
+ println!("{}", output.stderr);
+ Err(())
+ }
+ }
+}
+
+fn prompt_for_completion(exercise: &Exercise, prompt_output: Option<String>) -> bool {
+ let context = match exercise.state() {
+ State::Done => return true,
+ State::Pending(context) => context,
+ };
+
+ let no_emoji = env::var("NO_EMOJI").is_ok();
+
+ let clippy_success_msg = if no_emoji {
+ "The code is compiling, and Clippy is happy!"
+ } else {
+ "The code is compiling, and 📎 Clippy 📎 is happy!"
+ };
+
+ let success_msg = match exercise.mode {
+ Mode::Compile => "The code is compiling!",
+ Mode::Test => "The code is compiling, and the tests pass!",
+ Mode::Clippy => clippy_success_msg,
+ };
+
+ println!();
+ if no_emoji {
+ println!("~*~ {success_msg} ~*~")
+ } else {
+ println!("🎉 🎉 {success_msg} 🎉 🎉")
+ }
+ println!();
+
+ if let Some(output) = prompt_output {
+ println!("Output:");
+ println!("{}", separator());
+ println!("{output}");
+ println!("{}", separator());
+ println!();
+ }
+
+ println!("You can keep working on this exercise,");
+ println!(
+ "or jump into the next one by removing the {} comment:",
+ style("`I AM NOT DONE`").bold()
+ );
+ println!();
+ for context_line in context {
+ let formatted_line = if context_line.important {
+ format!("{}", style(context_line.line).bold())
+ } else {
+ context_line.line.to_string()
+ };
+
+ println!(
+ "{:>2} {} {}",
+ style(context_line.number).blue().bold(),
+ style("|").blue(),
+ formatted_line
+ );
+ }
+
+ false
+}
+
+fn separator() -> console::StyledObject<&'static str> {
+ style("====================").bold()
+}
+
fn:
) to \
+ restrict the search to a given item kind.","Accepted kinds are: fn
, mod
, struct
, \
+ enum
, trait
, type
, macro
, \
+ and const
.","Search functions by type signature (e.g., vec -> usize
or \
+ -> vec
or String, enum:Cow -> bool
)","You can look for items with an exact name by putting double quotes around \
+ your request: \"string\"
","Look for functions that accept or return \
+ slices and \
+ arrays by writing \
+ square brackets (e.g., -> [u8]
or [] -> Option
)","Look for items inside another one by searching for a path: vec::Vec
",].map(x=>""+x+"
").join("");const div_infos=document.createElement("div");addClass(div_infos,"infos");div_infos.innerHTML="${value.replaceAll(" ", " ")}
`}else{error[index]=value}});output+=`:wrench: Rust prototypes.
+Contributions are greatly appreciated. +Please fork this repository and open a pull request.
+