Merge branch 'rust-lang:main' into main

This commit is contained in:
Polycarbohydrate 2026-01-08 20:48:21 -05:00 committed by GitHub
commit 5a3f75f240
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
77 changed files with 1308 additions and 913 deletions

2
.cargo/config.toml Normal file
View File

@ -0,0 +1,2 @@
[alias]
dev = ["run", "--", "dev"]

View File

@ -1,7 +0,0 @@
root = true
[*.rs]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4

View File

@ -1,10 +1,16 @@
name: Rustlings Tests name: Check
on: on:
push: push:
branches: [main] branches: [main]
paths-ignore:
- website
- '*.md'
pull_request: pull_request:
branches: [main] branches: [main]
paths-ignore:
- website
- '*.md'
env: env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
@ -14,30 +20,28 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- run: cargo clippy -- --deny warnings - name: Clippy
run: cargo clippy -- --deny warnings
fmt: fmt:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: DavidAnson/markdownlint-cli2-action@v16 - name: rustfmt
with:
globs: "exercises/**/*.md"
- name: Run cargo fmt
run: cargo fmt --all --check run: cargo fmt --all --check
test: test:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, windows-latest, macOS-latest] os: [ubuntu-latest, windows-latest, macos-latest]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: swatinem/rust-cache@v2 - uses: swatinem/rust-cache@v2
- name: Run cargo test - name: cargo test
run: cargo test --workspace run: cargo test --workspace
dev-check: dev-check:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: swatinem/rust-cache@v2 - uses: swatinem/rust-cache@v2
- name: Run rustlings dev check - name: rustlings dev check
run: cargo run -- dev check --require-solutions run: cargo dev check --require-solutions

View File

@ -1,87 +0,0 @@
# Workflow to build your docs with oranda (and mdbook)
# and deploy them to Github Pages
name: Web
# We're going to push to the gh-pages branch, so we need that permission
permissions:
contents: write
# What situations do we want to build docs in?
# All of these work independently and can be removed / commented out
# if you don't want oranda/mdbook running in that situation
on:
# Check that a PR didn't break docs!
#
# Note that the "Deploy to Github Pages" step won't run in this mode,
# so this won't have any side-effects. But it will tell you if a PR
# completely broke oranda/mdbook. Sadly we don't provide previews (yet)!
pull_request:
# Whenever something gets pushed to main, update the docs!
# This is great for getting docs changes live without cutting a full release.
#
# Note that if you're using cargo-dist, this will "race" the Release workflow
# that actually builds the Github Release that oranda tries to read (and
# this will almost certainly complete first). As a result you will publish
# docs for the latest commit but the oranda landing page won't know about
# the latest release. The workflow_run trigger below will properly wait for
# cargo-dist, and so this half-published state will only last for ~10 minutes.
#
# If you only want docs to update with releases, disable this, or change it to
# a "release" branch. You can, of course, also manually trigger a workflow run
# when you want the docs to update.
push:
branches:
- main
# Whenever a workflow called "Release" completes, update the docs!
#
# If you're using cargo-dist, this is recommended, as it will ensure that
# oranda always sees the latest release right when it's available. Note
# however that Github's UI is wonky when you use workflow_run, and won't
# show this workflow as part of any commit. You have to go to the "actions"
# tab for your repo to see this one running (the gh-pages deploy will also
# only show up there).
workflow_run:
workflows: [ "Release" ]
types:
- completed
# Alright, let's do it!
jobs:
web:
name: Build and deploy site and docs
runs-on: ubuntu-latest
steps:
# Setup
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: swatinem/rust-cache@v2
# If you use any mdbook plugins, here's the place to install them!
# Install and run oranda (and mdbook)
# This will write all output to ./public/ (including copying mdbook's output to there)
- name: Install and run oranda
run: |
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/oranda/releases/download/v0.3.1/oranda-installer.sh | sh
oranda build
# Deploy to our gh-pages branch (creating it if it doesn't exist)
# the "public" dir that oranda made above will become the root dir
# of this branch.
#
# Note that once the gh-pages branch exists, you must
# go into repo's settings > pages and set "deploy from branch: gh-pages"
# the other defaults work fine.
- name: Deploy to Github Pages
uses: JamesIves/github-pages-deploy-action@v4.4.1
# ONLY if we're on main (so no PRs or feature branches allowed!)
if: ${{ github.ref == 'refs/heads/main' }}
with:
branch: gh-pages
# Gotta tell the action where to find oranda's output
folder: public
token: ${{ secrets.GITHUB_TOKEN }}
single-commit: true

43
.github/workflows/website.yml vendored Normal file
View File

@ -0,0 +1,43 @@
name: Website
on:
workflow_dispatch:
push:
branches: [main]
paths: [website]
jobs:
build:
defaults:
run:
working-directory: website
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install TailwindCSS
run: npm install
- name: Build CSS
run: npx @tailwindcss/cli -m -i input.css -o static/main.css
- name: Download Zola
run: curl -fsSL https://github.com/getzola/zola/releases/download/v0.20.0/zola-v0.20.0-x86_64-unknown-linux-gnu.tar.gz | tar xz
- name: Build site
run: ./zola build
- name: Upload static files as artifact
uses: actions/upload-pages-artifact@v3
with:
path: website/public/
deploy:
needs: build
# Grant GITHUB_TOKEN the permissions required to make a Pages deployment
permissions:
pages: write # to deploy to Pages
id-token: write # to verify the deployment originates from an appropriate source
# Deploy to the github-pages environment
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Deploy to GitHub Pages
uses: actions/deploy-pages@v4

4
.gitignore vendored
View File

@ -6,10 +6,6 @@ Cargo.lock
# State file # State file
.rustlings-state.txt .rustlings-state.txt
# oranda
public/
.netlify
# OS # OS
.DS_Store .DS_Store
.direnv/ .direnv/

View File

@ -1,2 +0,0 @@
# MD013/line-length Line length, Expected: 80
MD013: false

View File

@ -2,10 +2,26 @@
### Changed ### Changed
- Upgrade to Rust edition 2024 - `vecs2`: Removed the use of `map` and `collect`, which are only taught later.
- Raise the minimum supported Rust version to `1.85`
<a name="6.4.0"></a> ## 6.5.0 (2025-08-21)
### Added
- Check that Clippy is installed before initialization
### Changed
- Upgrade to Rust edition 2024
- Raise the minimum supported Rust version to `1.88`
- Don't follow symlinks in the file watcher
- `dev new`: Don't add `.rustlings-state.txt` to `.gitignore`
### Fixed
- Fix file links in VS Code
- Fix error printing when the progress bar is shown
- `dev check`: Don't check formatting if there are no solution files
## 6.4.0 (2024-11-11) ## 6.4.0 (2024-11-11)
@ -18,7 +34,7 @@
- New option `x` in the prompt to reset the file of the current exercise 🔄 - New option `x` in the prompt to reset the file of the current exercise 🔄
- Allow `dead_code` for all exercises and solutions ⚰️ (thanks to [@huss4in](https://github.com/huss4in)) - Allow `dead_code` for all exercises and solutions ⚰️ (thanks to [@huss4in](https://github.com/huss4in))
- Pause input while running an exercise to avoid unexpected prompt interactions ⏸️ - Pause input while running an exercise to avoid unexpected prompt interactions ⏸️
- Limit the maximum number of exercises to 999. Any third-party exercises willing to reach that limit? 🔝 - Limit the maximum number of exercises to 999. Any community exercises willing to reach that limit? 🔝
### Changed ### Changed
@ -36,8 +52,6 @@
- Fix bad contrast in the list on terminals with a light theme. - Fix bad contrast in the list on terminals with a light theme.
<a name="6.3.0"></a>
## 6.3.0 (2024-08-29) ## 6.3.0 (2024-08-29)
### Added ### Added
@ -77,8 +91,6 @@
- Fix the list when the terminal height is too low. - Fix the list when the terminal height is too low.
- Restore the terminal after an error in the list. - Restore the terminal after an error in the list.
<a name="6.2.0"></a>
## 6.2.0 (2024-08-09) ## 6.2.0 (2024-08-09)
### Added ### Added
@ -95,13 +107,11 @@
- Run the final check of all exercises in parallel. - Run the final check of all exercises in parallel.
- Small exercise improvements. - Small exercise improvements.
<a name="6.1.0"></a>
## 6.1.0 (2024-07-10) ## 6.1.0 (2024-07-10)
#### Added #### Added
- `dev check`: Check that all exercises (including third-party ones) include at least one `TODO` comment. - `dev check`: Check that all exercises (including community ones) include at least one `TODO` comment.
- `dev check`: Check that all exercises actually fail to run (not already solved). - `dev check`: Check that all exercises actually fail to run (not already solved).
#### Changed #### Changed
@ -114,15 +124,11 @@
- Exit with a helpful error message on missing/unsupported terminal/TTY. - Exit with a helpful error message on missing/unsupported terminal/TTY.
- Mark the last exercise as done. - Mark the last exercise as done.
<a name="6.0.1"></a>
## 6.0.1 (2024-07-04) ## 6.0.1 (2024-07-04)
Small exercise improvements and fixes. Small exercise improvements and fixes.
Most importantly, fixed that the exercise `clippy1` was already solved 😅 Most importantly, fixed that the exercise `clippy1` was already solved 😅
<a name="6.0.0"></a>
## 6.0.0 (2024-07-03) ## 6.0.0 (2024-07-03)
This release is the result of a complete rewrite to deliver a ton of new features and improvements ✨ This release is the result of a complete rewrite to deliver a ton of new features and improvements ✨
@ -180,15 +186,13 @@ This should avoid issues related to the language server or to running exercises,
Clippy lints are now shown on all exercises, not only the Clippy exercises 📎 Clippy lints are now shown on all exercises, not only the Clippy exercises 📎
Make Clippy your friend from early on 🥰 Make Clippy your friend from early on 🥰
### Third-party exercises ### Community Exercises
Rustlings now supports third-party exercises! Rustlings now supports community exercises!
Do you want to create your own set of Rustlings exercises to focus on some specific topic? Do you want to create your own set of Rustlings exercises to focus on some specific topic?
Or do you want to translate the original Rustlings exercises? Or do you want to translate the original Rustlings exercises?
Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXERCISES.md)! Then follow the link to the guide about [community exercises](https://rustlings.rust-lang.org/community-exercises)!
<a name="5.6.1"></a>
## 5.6.1 (2023-09-18) ## 5.6.1 (2023-09-18)
@ -205,8 +209,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- `as_ref_mut`: Fixed a typo in a test function name. - `as_ref_mut`: Fixed a typo in a test function name.
- `enums3`: Fixed formatting with `rustfmt`. - `enums3`: Fixed formatting with `rustfmt`.
<a name="5.6.0"></a>
## 5.6.0 (2023-09-04) ## 5.6.0 (2023-09-04)
#### Added #### Added
@ -246,16 +248,12 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- Lots of Nix housekeeping that I don't feel qualified to write about! - Lots of Nix housekeeping that I don't feel qualified to write about!
- Improved CI workflows, we're now testing on multiple platforms at once. - Improved CI workflows, we're now testing on multiple platforms at once.
<a name="5.5.1"></a>
## 5.5.1 (2023-05-17) ## 5.5.1 (2023-05-17)
#### Fixed #### Fixed
- Reverted `rust-project.json` path generation due to an upstream `rust-analyzer` fix. - Reverted `rust-project.json` path generation due to an upstream `rust-analyzer` fix.
<a name="5.5.0"></a>
## 5.5.0 (2023-05-17) ## 5.5.0 (2023-05-17)
#### Added #### Added
@ -290,8 +288,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- Added a markdown linter to run on GitHub actions - Added a markdown linter to run on GitHub actions
- Split quick installation section into two code blocks - Split quick installation section into two code blocks
<a name="5.4.1"></a>
## 5.4.1 (2023-03-10) ## 5.4.1 (2023-03-10)
#### Changed #### Changed
@ -307,8 +303,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- `macros4`: Prevented auto-fix by adding `#[rustfmt::skip]` - `macros4`: Prevented auto-fix by adding `#[rustfmt::skip]`
- `cli`: Actually show correct progress percentages - `cli`: Actually show correct progress percentages
<a name="5.4.0"></a>
## 5.4.0 (2023-02-12) ## 5.4.0 (2023-02-12)
#### Changed #### Changed
@ -337,8 +331,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- Bumped min Rust version to 1.58 in installation script - Bumped min Rust version to 1.58 in installation script
<a name="5.3.0"></a>
## 5.3.0 (2022-12-23) ## 5.3.0 (2022-12-23)
#### Added #### Added
@ -371,8 +363,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- Applied some Clippy and rustfmt formatting - Applied some Clippy and rustfmt formatting
- Added a note on Windows PowerShell and other shell compatibility - Added a note on Windows PowerShell and other shell compatibility
<a name="5.2.1"></a>
## 5.2.1 (2022-09-06) ## 5.2.1 (2022-09-06)
#### Fixed #### Fixed
@ -386,8 +376,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- Fixed a typo in README.md - Fixed a typo in README.md
<a name="5.2.0"></a>
## 5.2.0 (2022-08-27) ## 5.2.0 (2022-08-27)
#### Added #### Added
@ -404,16 +392,12 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- **quiz1**: Adjusted the explanations to be consistent with - **quiz1**: Adjusted the explanations to be consistent with
the tests the tests
<a name="5.1.1"></a>
## 5.1.1 (2022-08-17) ## 5.1.1 (2022-08-17)
#### Bug Fixes #### Bug Fixes
- Fixed an incorrect assertion in options1 - Fixed an incorrect assertion in options1
<a name="5.1.0"></a>
## 5.1.0 (2022-08-16) ## 5.1.0 (2022-08-16)
#### Features #### Features
@ -448,8 +432,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- Clarified manual installation instructions using `cargo install --path .` - Clarified manual installation instructions using `cargo install --path .`
- Added a link to our Zulip in the readme file - Added a link to our Zulip in the readme file
<a name="5.0.0"></a>
## 5.0.0 (2022-07-16) ## 5.0.0 (2022-07-16)
#### Features #### Features
@ -522,8 +504,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- Updated spacing in Cargo.toml. - Updated spacing in Cargo.toml.
- Added a GitHub actions config so that tests run on every PR/commit. - Added a GitHub actions config so that tests run on every PR/commit.
<a name="4.8.0"></a>
## 4.8.0 (2022-07-01) ## 4.8.0 (2022-07-01)
#### Features #### Features
@ -544,8 +524,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- Replaced the git.io URL with the fully qualified URL because of git.io's sunsetting. - Replaced the git.io URL with the fully qualified URL because of git.io's sunsetting.
- Removed the deprecated Rust GitPod extension. - Removed the deprecated Rust GitPod extension.
<a name="4.7.1"></a>
## 4.7.1 (2022-04-20) ## 4.7.1 (2022-04-20)
#### Features #### Features
@ -566,8 +544,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- The changelog will now be manually written instead of being automatically generated by the - The changelog will now be manually written instead of being automatically generated by the
Git log. Git log.
<a name="4.7.0"></a>
## 4.7.0 (2022-04-14) ## 4.7.0 (2022-04-14)
#### Features #### Features
@ -608,8 +584,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- Add hints on how to get GCC installed (#741) ([bc56861](https://github.com/rust-lang/rustlings/commit/bc5686174463ad6f4f6b824b0e9b97c3039d4886)) - Add hints on how to get GCC installed (#741) ([bc56861](https://github.com/rust-lang/rustlings/commit/bc5686174463ad6f4f6b824b0e9b97c3039d4886))
- Fix some code blocks that were not highlighted ([17f9d74](https://github.com/rust-lang/rustlings/commit/17f9d7429ccd133a72e815fb5618e0ce79560929)) - Fix some code blocks that were not highlighted ([17f9d74](https://github.com/rust-lang/rustlings/commit/17f9d7429ccd133a72e815fb5618e0ce79560929))
<a name="4.6.0"></a>
## 4.6.0 (2021-09-25) ## 4.6.0 (2021-09-25)
#### Features #### Features
@ -632,8 +606,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- Clarify instructions ([df25684c](https://github.com/rust-lang/rustlings/commit/df25684cb79f8413915e00b5efef29369849cef1)) - Clarify instructions ([df25684c](https://github.com/rust-lang/rustlings/commit/df25684cb79f8413915e00b5efef29369849cef1))
- **quiz1:** Fix inconsistent wording (#826) ([03131a3d](https://github.com/rust-lang/rustlings/commit/03131a3d35d9842598150f9da817f7cc26e2669a)) - **quiz1:** Fix inconsistent wording (#826) ([03131a3d](https://github.com/rust-lang/rustlings/commit/03131a3d35d9842598150f9da817f7cc26e2669a))
<a name="4.5.0"></a>
## 4.5.0 (2021-07-07) ## 4.5.0 (2021-07-07)
#### Features #### Features
@ -654,8 +626,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- **try_from_into, from_str:** hints for dyn Error ([11d2cf0d](https://github.com/rust-lang/rustlings/commit/11d2cf0d604dee3f5023c17802d69438e69fa50e)) - **try_from_into, from_str:** hints for dyn Error ([11d2cf0d](https://github.com/rust-lang/rustlings/commit/11d2cf0d604dee3f5023c17802d69438e69fa50e))
- **variables5:** confine the answer further ([48ffcbd2](https://github.com/rust-lang/rustlings/commit/48ffcbd2c4cc4d936c2c7480019190f179813cc5)) - **variables5:** confine the answer further ([48ffcbd2](https://github.com/rust-lang/rustlings/commit/48ffcbd2c4cc4d936c2c7480019190f179813cc5))
<a name="4.4.0"></a>
## 4.4.0 (2021-04-24) ## 4.4.0 (2021-04-24)
#### Bug Fixes #### Bug Fixes
@ -697,8 +667,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- updated progress percentage ([1c6f7e4b](https://github.com/rust-lang/rustlings/commit/1c6f7e4b7b9b3bd36f4da2bb2b69c549cc8bd913)) - updated progress percentage ([1c6f7e4b](https://github.com/rust-lang/rustlings/commit/1c6f7e4b7b9b3bd36f4da2bb2b69c549cc8bd913))
- added progress info ([c0e3daac](https://github.com/rust-lang/rustlings/commit/c0e3daacaf6850811df5bc57fa43e0f249d5cfa4)) - added progress info ([c0e3daac](https://github.com/rust-lang/rustlings/commit/c0e3daacaf6850811df5bc57fa43e0f249d5cfa4))
<a name="4.3.0"></a>
## 4.3.0 (2020-12-29) ## 4.3.0 (2020-12-29)
#### Features #### Features
@ -721,8 +689,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- Update description (#584) ([96347df9](https://github.com/rust-lang/rustlings/commit/96347df9df294f01153b29d9ad4ba361f665c755)) - Update description (#584) ([96347df9](https://github.com/rust-lang/rustlings/commit/96347df9df294f01153b29d9ad4ba361f665c755))
- **vec1:** Have test compare every element in a and v ([9b6c6293](https://github.com/rust-lang/rustlings/commit/9b6c629397b24b944f484f5b2bbd8144266b5695)) - **vec1:** Have test compare every element in a and v ([9b6c6293](https://github.com/rust-lang/rustlings/commit/9b6c629397b24b944f484f5b2bbd8144266b5695))
<a name="4.2.0"></a>
## 4.2.0 (2020-11-07) ## 4.2.0 (2020-11-07)
#### Features #### Features
@ -743,8 +709,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- missing comma in test ([4fb230da](https://github.com/rust-lang/rustlings/commit/4fb230daf1251444fcf29e085cee222a91f8a37e)) - missing comma in test ([4fb230da](https://github.com/rust-lang/rustlings/commit/4fb230daf1251444fcf29e085cee222a91f8a37e))
- **quiz3:** Second test is for odd numbers, not even. (#553) ([18e0bfef](https://github.com/rust-lang/rustlings/commit/18e0bfef1de53071e353ba1ec5837002ff7290e6)) - **quiz3:** Second test is for odd numbers, not even. (#553) ([18e0bfef](https://github.com/rust-lang/rustlings/commit/18e0bfef1de53071e353ba1ec5837002ff7290e6))
<a name="4.1.0"></a>
## 4.1.0 (2020-10-05) ## 4.1.0 (2020-10-05)
#### Bug Fixes #### Bug Fixes
@ -767,8 +731,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- **cli:** Added 'cls' command to 'watch' mode (#474) ([4f2468e1](https://github.com/rust-lang/rustlings/commit/4f2468e14f574a93a2e9b688367b5752ed96ae7b)) - **cli:** Added 'cls' command to 'watch' mode (#474) ([4f2468e1](https://github.com/rust-lang/rustlings/commit/4f2468e14f574a93a2e9b688367b5752ed96ae7b))
- **try_from_into:** Add insufficient length test (#469) ([523d18b8](https://github.com/rust-lang/rustlings/commit/523d18b873a319f7c09262f44bd40e2fab1830e5)) - **try_from_into:** Add insufficient length test (#469) ([523d18b8](https://github.com/rust-lang/rustlings/commit/523d18b873a319f7c09262f44bd40e2fab1830e5))
<a name="4.0.0"></a>
## 4.0.0 (2020-07-08) ## 4.0.0 (2020-07-08)
#### Breaking Changes #### Breaking Changes
@ -810,8 +772,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- **test2:** name of type String and &str (#394) ([d6c0a688](https://github.com/rust-lang/rustlings/commit/d6c0a688e6a96f93ad60d540d4b326f342fc0d45)) - **test2:** name of type String and &str (#394) ([d6c0a688](https://github.com/rust-lang/rustlings/commit/d6c0a688e6a96f93ad60d540d4b326f342fc0d45))
- **variables6:** minor typo (#419) ([524e17df](https://github.com/rust-lang/rustlings/commit/524e17df10db95f7b90a0f75cc8997182a8a4094)) - **variables6:** minor typo (#419) ([524e17df](https://github.com/rust-lang/rustlings/commit/524e17df10db95f7b90a0f75cc8997182a8a4094))
<a name="3.0.0"></a>
## 3.0.0 (2020-04-11) ## 3.0.0 (2020-04-11)
#### Breaking Changes #### Breaking Changes
@ -834,8 +794,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- add new exercises for generics (#280) ([76be5e4e](https://github.com/rust-lang/rustlings/commit/76be5e4e991160f5fd9093f03ee2ba260e8f7229)) - add new exercises for generics (#280) ([76be5e4e](https://github.com/rust-lang/rustlings/commit/76be5e4e991160f5fd9093f03ee2ba260e8f7229))
- **ci:** add buildkite config ([b049fa2c](https://github.com/rust-lang/rustlings/commit/b049fa2c84dba0f0c8906ac44e28fd45fba51a71)) - **ci:** add buildkite config ([b049fa2c](https://github.com/rust-lang/rustlings/commit/b049fa2c84dba0f0c8906ac44e28fd45fba51a71))
<a name="2.2.1"></a>
### 2.2.1 (2020-02-27) ### 2.2.1 (2020-02-27)
#### Bug Fixes #### Bug Fixes
@ -846,8 +804,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- Add clippy lints (#269) ([1e2fd9c9](https://github.com/rust-lang/rustlings/commit/1e2fd9c92f8cd6e389525ca1a999fca4c90b5921)) - Add clippy lints (#269) ([1e2fd9c9](https://github.com/rust-lang/rustlings/commit/1e2fd9c92f8cd6e389525ca1a999fca4c90b5921))
<a name="2.2.0"></a>
## 2.2.0 (2020-02-25) ## 2.2.0 (2020-02-25)
#### Bug Fixes #### Bug Fixes
@ -875,8 +831,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- Added traits exercises (#274 but specifically #216, which originally added - Added traits exercises (#274 but specifically #216, which originally added
this :heart:) ([b559cdd](https://github.com/rust-lang/rustlings/commit/b559cdd73f32c0d0cfc1feda39f82b3e3583df17)) this :heart:) ([b559cdd](https://github.com/rust-lang/rustlings/commit/b559cdd73f32c0d0cfc1feda39f82b3e3583df17))
<a name="2.1.0"></a>
## 2.1.0 (2019-11-27) ## 2.1.0 (2019-11-27)
#### Bug Fixes #### Bug Fixes
@ -894,8 +848,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- **watch:** show hint while watching ([8143d57b](https://github.com/rust-lang/rustlings/commit/8143d57b4e88c51341dd4a18a14c536042cc009c)) - **watch:** show hint while watching ([8143d57b](https://github.com/rust-lang/rustlings/commit/8143d57b4e88c51341dd4a18a14c536042cc009c))
<a name="2.0.0"></a>
## 2.0.0 (2019-11-12) ## 2.0.0 (2019-11-12)
#### Bug Fixes #### Bug Fixes
@ -916,8 +868,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- **cli:** check for rustc before doing anything ([36a033b8](https://github.com/rust-lang/rustlings/commit/36a033b87a6549c1e5639c908bf7381c84f4f425)) - **cli:** check for rustc before doing anything ([36a033b8](https://github.com/rust-lang/rustlings/commit/36a033b87a6549c1e5639c908bf7381c84f4f425))
- **hint:** Add test for hint ([ce9fa6eb](https://github.com/rust-lang/rustlings/commit/ce9fa6ebbfdc3e7585d488d9409797285708316f)) - **hint:** Add test for hint ([ce9fa6eb](https://github.com/rust-lang/rustlings/commit/ce9fa6ebbfdc3e7585d488d9409797285708316f))
<a name="1.5.1"></a>
### 1.5.1 (2019-11-11) ### 1.5.1 (2019-11-11)
#### Bug Fixes #### Bug Fixes
@ -929,8 +879,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- **threads:** Move Threads behind SLT ([fbe91a67](https://github.com/rust-lang/rustlings/commit/fbe91a67a482bfe64cbcdd58d06ba830a0f39da3), closes [#205](https://github.com/rust-lang/rustlings/issues/205)) - **threads:** Move Threads behind SLT ([fbe91a67](https://github.com/rust-lang/rustlings/commit/fbe91a67a482bfe64cbcdd58d06ba830a0f39da3), closes [#205](https://github.com/rust-lang/rustlings/issues/205))
- **watch:** clear screen before each `verify()` ([3aff590](https://github.com/rust-lang/rustlings/commit/3aff59085586c24196a547c2693adbdcf4432648)) - **watch:** clear screen before each `verify()` ([3aff590](https://github.com/rust-lang/rustlings/commit/3aff59085586c24196a547c2693adbdcf4432648))
<a name="1.5.0"></a>
## 1.5.0 (2019-11-09) ## 1.5.0 (2019-11-09)
#### Bug Fixes #### Bug Fixes
@ -955,8 +903,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- Added exercise for struct update syntax ([1c4c8764](https://github.com/rust-lang/rustlings/commit/1c4c8764ed118740cd4cee73272ddc6cceb9d959)) - Added exercise for struct update syntax ([1c4c8764](https://github.com/rust-lang/rustlings/commit/1c4c8764ed118740cd4cee73272ddc6cceb9d959))
- **iterators2:** adds iterators2 exercise including config ([9288fccf](https://github.com/rust-lang/rustlings/commit/9288fccf07a2c5043b76d0fd6491e4cf72d76031)) - **iterators2:** adds iterators2 exercise including config ([9288fccf](https://github.com/rust-lang/rustlings/commit/9288fccf07a2c5043b76d0fd6491e4cf72d76031))
<a name="1.4.1"></a>
### 1.4.1 (2019-08-13) ### 1.4.1 (2019-08-13)
#### Bug Fixes #### Bug Fixes
@ -965,8 +911,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- **option1:** Add test for prematurely passing exercise ([a750e4a1](https://github.com/rust-lang/rustlings/commit/a750e4a1a3006227292bb17d57d78ce84da6bfc6)) - **option1:** Add test for prematurely passing exercise ([a750e4a1](https://github.com/rust-lang/rustlings/commit/a750e4a1a3006227292bb17d57d78ce84da6bfc6))
- **test1:** Swap assertion parameter order ([4086d463](https://github.com/rust-lang/rustlings/commit/4086d463a981e81d97781851d17db2ced290f446)) - **test1:** Swap assertion parameter order ([4086d463](https://github.com/rust-lang/rustlings/commit/4086d463a981e81d97781851d17db2ced290f446))
<a name="1.4.0"></a>
## 1.4.0 (2019-07-13) ## 1.4.0 (2019-07-13)
#### Bug Fixes #### Bug Fixes
@ -983,8 +927,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- **changelog:** Use clog for changelogs ([34e31232](https://github.com/rust-lang/rustlings/commit/34e31232dfddde284a341c9609b33cd27d9d5724)) - **changelog:** Use clog for changelogs ([34e31232](https://github.com/rust-lang/rustlings/commit/34e31232dfddde284a341c9609b33cd27d9d5724))
- **iterators2:** adds iterators2 exercise including config ([9288fccf](https://github.com/rust-lang/rustlings/commit/9288fccf07a2c5043b76d0fd6491e4cf72d76031)) - **iterators2:** adds iterators2 exercise including config ([9288fccf](https://github.com/rust-lang/rustlings/commit/9288fccf07a2c5043b76d0fd6491e4cf72d76031))
<a name="1.3.0"></a>
### 1.3.0 (2019-06-05) ### 1.3.0 (2019-06-05)
#### Features #### Features
@ -1000,16 +942,12 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- Fix broken link (#164, @HanKruiger) - Fix broken link (#164, @HanKruiger)
- Remove highlighting and syntect (#167, @komaeda) - Remove highlighting and syntect (#167, @komaeda)
<a name="1.2.2"></a>
### 1.2.2 (2019-05-07) ### 1.2.2 (2019-05-07)
#### Bug Fixes #### Bug Fixes
- Reverted `--nocapture` flag since it was causing tests to pass unconditionally - Reverted `--nocapture` flag since it was causing tests to pass unconditionally
<a name="1.2.1"></a>
### 1.2.1 (2019-04-22) ### 1.2.1 (2019-04-22)
#### Bug Fixes #### Bug Fixes
@ -1017,8 +955,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- Fix the `--nocapture` feature (@komaeda) - Fix the `--nocapture` feature (@komaeda)
- Provide a nicer error message for when you're in the wrong directory - Provide a nicer error message for when you're in the wrong directory
<a name="1.2.0"></a>
### 1.2.0 (2019-04-22) ### 1.2.0 (2019-04-22)
#### Features #### Features
@ -1026,8 +962,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- Add errors to exercises that compile without user changes (@yvan-sraka) - Add errors to exercises that compile without user changes (@yvan-sraka)
- Use --nocapture when testing, enabling `println!` when running (@komaeda) - Use --nocapture when testing, enabling `println!` when running (@komaeda)
<a name="1.1.1"></a>
### 1.1.1 (2019-04-14) ### 1.1.1 (2019-04-14)
#### Bug fixes #### Bug fixes
@ -1040,8 +974,6 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- Fix links by deleting book version (@diodfr, #142) - Fix links by deleting book version (@diodfr, #142)
- Canonicalize paths to fix path matching (@cjpearce, #143) - Canonicalize paths to fix path matching (@cjpearce, #143)
<a name="1.1.0"></a>
### 1.1.0 (2019-03-20) ### 1.1.0 (2019-03-20)
- errors2.rs: update link to Rust book (#124) - errors2.rs: update link to Rust book (#124)
@ -1051,16 +983,12 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
- Give a warning when Rustlings isn't run from the right directory (#123) - Give a warning when Rustlings isn't run from the right directory (#123)
- Verify that rust version is recent enough to install Rustlings (#131) - Verify that rust version is recent enough to install Rustlings (#131)
<a name="1.0.1"></a>
### 1.0.1 (2019-03-06) ### 1.0.1 (2019-03-06)
- Adds a way to install Rustlings in one command (`curl -L https://git.io/rustlings | bash`) - Adds a way to install Rustlings in one command (`curl -L https://git.io/rustlings | bash`)
- Makes `rustlings watch` react to create file events (@shaunbennett, #117) - Makes `rustlings watch` react to create file events (@shaunbennett, #117)
- Reworks the exercise management to use an external TOML file instead of just listing them in the code - Reworks the exercise management to use an external TOML file instead of just listing them in the code
<a name="1.0.0"></a>
### 1.0.0 (2019-03-06) ### 1.0.0 (2019-03-06)
Initial release. Initial release.

459
Cargo.lock generated
View File

@ -4,9 +4,9 @@ version = 4
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.18" version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"anstyle-parse", "anstyle-parse",
@ -19,50 +19,50 @@ dependencies = [
[[package]] [[package]]
name = "anstyle" name = "anstyle"
version = "1.0.10" version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
[[package]] [[package]]
name = "anstyle-parse" name = "anstyle-parse"
version = "0.2.6" version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [ dependencies = [
"utf8parse", "utf8parse",
] ]
[[package]] [[package]]
name = "anstyle-query" name = "anstyle-query"
version = "1.1.2" version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
name = "anstyle-wincon" name = "anstyle-wincon"
version = "3.0.7" version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"once_cell", "once_cell_polyfill",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.96" version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.4.0" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
@ -72,21 +72,21 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.8.0" version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.31" version = "4.5.45"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -94,9 +94,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.31" version = "4.5.44"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -106,9 +106,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.28" version = "4.5.45"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -118,24 +118,25 @@ dependencies = [
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.7.4" version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
version = "1.0.3" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]] [[package]]
name = "crossterm" name = "crossterm"
version = "0.28.1" version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.2",
"crossterm_winapi", "crossterm_winapi",
"document-features",
"mio", "mio",
"parking_lot", "parking_lot",
"rustix", "rustix",
@ -153,6 +154,15 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "document-features"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d"
dependencies = [
"litrs",
]
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"
@ -161,12 +171,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.10" version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@ -175,18 +185,6 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "filetime"
version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
dependencies = [
"cfg-if",
"libc",
"libredox",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "fsevent-sys" name = "fsevent-sys"
version = "4.1.0" version = "4.1.0"
@ -198,21 +196,21 @@ dependencies = [
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.3.1" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"wasi 0.13.3+wasi-0.2.2", "r-efi",
"windows-targets", "wasi 0.14.2+wasi-0.2.4",
] ]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.2" version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
[[package]] [[package]]
name = "heck" name = "heck"
@ -222,9 +220,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.7.1" version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown",
@ -236,7 +234,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.2",
"inotify-sys", "inotify-sys",
"libc", "libc",
] ]
@ -258,15 +256,15 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.14" version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]] [[package]]
name = "kqueue" name = "kqueue"
version = "1.0.8" version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a"
dependencies = [ dependencies = [
"kqueue-sys", "kqueue-sys",
"libc", "libc",
@ -284,32 +282,27 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.170" version = "0.2.175"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[package]]
name = "libredox"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags 2.8.0",
"libc",
"redox_syscall",
]
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.15" version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "litrs"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.12" version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"scopeguard", "scopeguard",
@ -317,36 +310,35 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.26" version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.4" version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]] [[package]]
name = "mio" name = "mio"
version = "1.0.3" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
"wasi 0.11.0+wasi-snapshot-preview1", "wasi 0.11.1+wasi-snapshot-preview1",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
name = "notify" name = "notify"
version = "8.0.0" version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.2",
"filetime",
"fsevent-sys", "fsevent-sys",
"inotify", "inotify",
"kqueue", "kqueue",
@ -355,7 +347,7 @@ dependencies = [
"mio", "mio",
"notify-types", "notify-types",
"walkdir", "walkdir",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@ -366,25 +358,21 @@ checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d"
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.20.3" version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]] [[package]]
name = "os_pipe" name = "once_cell_polyfill"
version = "1.2.1" version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.3" version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
dependencies = [ dependencies = [
"lock_api", "lock_api",
"parking_lot_core", "parking_lot_core",
@ -392,88 +380,93 @@ dependencies = [
[[package]] [[package]]
name = "parking_lot_core" name = "parking_lot_core"
version = "0.9.10" version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"redox_syscall", "redox_syscall",
"smallvec", "smallvec",
"windows-targets", "windows-targets 0.52.6",
] ]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.93" version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.38" version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]] [[package]]
name = "redox_syscall" name = "r-efi"
version = "0.5.9" version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "redox_syscall"
version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.2",
] ]
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.44" version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.2",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
name = "rustlings" name = "rustlings"
version = "6.4.0" version = "6.5.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
"crossterm", "crossterm",
"notify", "notify",
"os_pipe",
"rustix", "rustix",
"rustlings-macros", "rustlings-macros",
"serde", "serde",
"serde_json", "serde_json",
"tempfile", "tempfile",
"toml_edit", "toml",
] ]
[[package]] [[package]]
name = "rustlings-macros" name = "rustlings-macros"
version = "6.4.0" version = "6.5.0"
dependencies = [ dependencies = [
"quote", "quote",
"serde", "serde",
"toml_edit", "toml",
] ]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.19" version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]] [[package]]
name = "same-file" name = "same-file"
@ -492,18 +485,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.218" version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.218" version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -512,9 +505,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.139" version = "1.0.143"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@ -524,18 +517,18 @@ dependencies = [
[[package]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "0.6.8" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83"
dependencies = [ dependencies = [
"serde", "serde",
] ]
[[package]] [[package]]
name = "signal-hook" name = "signal-hook"
version = "0.3.17" version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
dependencies = [ dependencies = [
"libc", "libc",
"signal-hook-registry", "signal-hook-registry",
@ -554,18 +547,18 @@ dependencies = [
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.2" version = "1.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b"
dependencies = [ dependencies = [
"libc", "libc",
] ]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.14.0" version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]] [[package]]
name = "strsim" name = "strsim"
@ -575,9 +568,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.98" version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -586,45 +579,61 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.17.1" version = "3.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e"
dependencies = [ dependencies = [
"cfg-if",
"fastrand", "fastrand",
"getrandom", "getrandom",
"once_cell", "once_cell",
"rustix", "rustix",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
name = "toml_datetime" name = "toml"
version = "0.6.8" version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
"toml_parser",
"toml_writer",
"winnow", "winnow",
] ]
[[package]] [[package]]
name = "unicode-ident" name = "toml_datetime"
version = "1.0.17" version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3"
dependencies = [
"serde",
]
[[package]]
name = "toml_parser"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10"
dependencies = [
"winnow",
]
[[package]]
name = "toml_writer"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64"
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
@ -644,15 +653,15 @@ dependencies = [
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.13.3+wasi-0.2.2" version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
dependencies = [ dependencies = [
"wit-bindgen-rt", "wit-bindgen-rt",
] ]
@ -675,11 +684,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]] [[package]]
name = "winapi-util" name = "winapi-util"
version = "0.1.9" version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@ -689,13 +698,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]] [[package]]
name = "windows-sys" name = "windows-link"
version = "0.52.0" version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
dependencies = [
"windows-targets",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
@ -703,7 +709,16 @@ version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [ dependencies = [
"windows-targets", "windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.3",
] ]
[[package]] [[package]]
@ -712,14 +727,31 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm", "windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc", "windows_aarch64_msvc 0.52.6",
"windows_i686_gnu", "windows_i686_gnu 0.52.6",
"windows_i686_gnullvm", "windows_i686_gnullvm 0.52.6",
"windows_i686_msvc", "windows_i686_msvc 0.52.6",
"windows_x86_64_gnu", "windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm", "windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc", "windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.53.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0",
"windows_i686_gnullvm 0.53.0",
"windows_i686_msvc 0.53.0",
"windows_x86_64_gnu 0.53.0",
"windows_x86_64_gnullvm 0.53.0",
"windows_x86_64_msvc 0.53.0",
] ]
[[package]] [[package]]
@ -728,42 +760,84 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]] [[package]]
name = "windows_i686_gnullvm" name = "windows_i686_gnullvm"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.52.6" version = "0.52.6"
@ -771,19 +845,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "winnow" name = "windows_x86_64_msvc"
version = "0.7.3" version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
dependencies = [
"memchr", [[package]]
] name = "winnow"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
[[package]] [[package]]
name = "wit-bindgen-rt" name = "wit-bindgen-rt"
version = "0.33.0" version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.2",
] ]

View File

@ -1,12 +1,11 @@
[workspace] [workspace]
resolver = "2"
exclude = [ exclude = [
"tests/test_exercises", "tests/test_exercises",
"dev", "dev",
] ]
[workspace.package] [workspace.package]
version = "6.4.0" version = "6.5.0"
authors = [ authors = [
"Mo Bitar <mo8it@proton.me>", # https://github.com/mo8it "Mo Bitar <mo8it@proton.me>", # https://github.com/mo8it
"Liv <mokou@fastmail.com>", # https://github.com/shadows-withal "Liv <mokou@fastmail.com>", # https://github.com/shadows-withal
@ -16,11 +15,11 @@ authors = [
repository = "https://github.com/rust-lang/rustlings" repository = "https://github.com/rust-lang/rustlings"
license = "MIT" license = "MIT"
edition = "2024" # On Update: Update the edition of `rustfmt` in `dev check` and `CARGO_TOML` in `dev new`. edition = "2024" # On Update: Update the edition of `rustfmt` in `dev check` and `CARGO_TOML` in `dev new`.
rust-version = "1.85" rust-version = "1.88"
[workspace.dependencies] [workspace.dependencies]
serde = { version = "1.0.218", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
toml_edit = { version = "0.22.24", default-features = false, features = ["parse", "serde"] } toml = { version = "0.9", default-features = false, features = ["std", "parse", "serde"] }
[package] [package]
name = "rustlings" name = "rustlings"
@ -46,21 +45,20 @@ include = [
] ]
[dependencies] [dependencies]
anyhow = "1.0.96" anyhow = "1.0"
clap = { version = "4.5.31", features = ["derive"] } clap = { version = "4.5", features = ["derive"] }
crossterm = { version = "0.28.1", default-features = false, features = ["windows", "events"] } crossterm = { version = "0.29", default-features = false, features = ["windows", "events"] }
notify = "8.0.0" notify = "8.0"
os_pipe = "1.2.1" rustlings-macros = { path = "rustlings-macros", version = "=6.5.0" }
rustlings-macros = { path = "rustlings-macros", version = "=6.4.0" } serde_json = "1.0"
serde_json = "1.0.139"
serde.workspace = true serde.workspace = true
toml_edit.workspace = true toml.workspace = true
[target.'cfg(not(windows))'.dependencies] [target.'cfg(not(windows))'.dependencies]
rustix = { version = "0.38.44", default-features = false, features = ["std", "stdio", "termios"] } rustix = { version = "1.0", default-features = false, features = ["std", "stdio", "termios"] }
[dev-dependencies] [dev-dependencies]
tempfile = "3.17.1" tempfile = "3.21"
[profile.release] [profile.release]
panic = "abort" panic = "abort"
@ -84,8 +82,6 @@ infinite_loop = "deny"
mem_forget = "deny" mem_forget = "deny"
dbg_macro = "warn" dbg_macro = "warn"
todo = "warn" todo = "warn"
# TODO: Remove after the following fix is released: https://github.com/rust-lang/rust-clippy/pull/13102
needless_option_as_deref = "allow"
[lints] [lints]
workspace = true workspace = true

167
README.md
View File

@ -1,166 +1,7 @@
<div class="oranda-hide"> # [Rustlings](https://rustlings.rust-lang.org) 🦀
# Rustlings 🦀❤️ Small exercises to get you used to reading and writing [Rust](https://www.rust-lang.org) code - _Recommended in parallel to reading [the official Rust book](https://doc.rust-lang.org/book) 📚_
</div> Visit the **website** for a demo, info about setup and more:
Greetings and welcome to Rustlings. ## ➡️ [rustlings.rust-lang.org](https://rustlings.rust-lang.org) ⬅️
This project contains small exercises to get you used to reading and writing Rust code.
This includes reading and responding to compiler messages!
It is recommended to do the Rustlings exercises in parallel to reading [the official Rust book](https://doc.rust-lang.org/book/), the most comprehensive resource for learning Rust 📚️
[Rust By Example](https://doc.rust-lang.org/rust-by-example/) is another recommended resource that you might find helpful.
It contains code examples and exercises similar to Rustlings, but online.
## Getting Started
### Installing Rust
Before installing Rustlings, you need to have the **latest version of Rust** installed.
Visit [www.rust-lang.org/tools/install](https://www.rust-lang.org/tools/install) for further instructions on installing Rust.
This will also install _Cargo_, Rust's package/project manager.
> 🐧 If you're on Linux, make sure you've installed `gcc` (for a linker).
>
> Deb: `sudo apt install gcc`.
> Dnf: `sudo dnf install gcc`.
> 🍎 If you're on MacOS, make sure you've installed Xcode and its developer tools by running `xcode-select --install`.
### Installing Rustlings
The following command will download and compile Rustlings:
```bash
cargo install rustlings
```
<details>
<summary><strong>If the installation fails…</strong> (<em>click to expand</em>)</summary>
- Make sure you have the latest Rust version by running `rustup update`
- Try adding the `--locked` flag: `cargo install rustlings --locked`
- Otherwise, please [report the issue](https://github.com/rust-lang/rustlings/issues/new)
</details>
### Initialization
After installing Rustlings, run the following command to initialize the `rustlings/` directory:
```bash
rustlings init
```
<details>
<summary><strong>If the command <code>rustlings</code> can't be found…</strong> (<em>click to expand</em>)</summary>
You are probably using Linux and installed Rust using your package manager.
Cargo installs binaries to the directory `~/.cargo/bin`.
Sadly, package managers often don't add `~/.cargo/bin` to your `PATH` environment variable.
The solution is to …
- either add `~/.cargo/bin` manually to `PATH`
- or to uninstall Rust from the package manager and install it using the official way with `rustup`: https://www.rust-lang.org/tools/install
</details>
Now, go into the newly initialized directory and launch Rustlings for further instructions on getting started with the exercises:
```bash
cd rustlings/
rustlings
```
## Working environment
### Editor
Our general recommendation is [VS Code](https://code.visualstudio.com/) with the [rust-analyzer plugin](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer).
But any editor that supports [rust-analyzer](https://rust-analyzer.github.io/) should be enough for working on the exercises.
### Terminal
While working with Rustlings, please use a modern terminal for the best user experience.
The default terminal on Linux and Mac should be sufficient.
On Windows, we recommend the [Windows Terminal](https://aka.ms/terminal).
## Doing exercises
The exercises are sorted by topic and can be found in the subdirectory `exercises/<topic>`.
For every topic, there is an additional `README.md` file with some resources to get you started on the topic.
We highly recommend that you have a look at them before you start 📚️
Most exercises contain an error that keeps them from compiling, and it's up to you to fix it!
Some exercises contain tests that need to pass for the exercise to be done ✅
Search for `TODO` and `todo!()` to find out what you need to change.
Ask for hints by entering `h` in the _watch mode_ 💡
### Watch Mode
After [initialization](#initialization), Rustlings can be launched by simply running the command `rustlings`.
This will start the _watch mode_ which walks you through the exercises in a predefined order (what we think is best for newcomers).
It will rerun the current exercise automatically every time you change the exercise's file in the `exercises/` directory.
<details>
<summary><strong>If detecting file changes in the <code>exercises/</code> directory fails…</strong> (<em>click to expand</em>)</summary>
> You can add the **`--manual-run`** flag (`rustlings --manual-run`) to manually rerun the current exercise by entering `r` in the watch mode.
>
> Please [report the issue](https://github.com/rust-lang/rustlings/issues/new) with some information about your operating system and whether you run Rustlings in a container or virtual machine (e.g. WSL).
</details>
### Exercise List
In the [watch mode](#watch-mode) (after launching `rustlings`), you can enter `l` to open the interactive exercise list.
The list allows you to…
- See the status of all exercises (done or pending)
- `c`: Continue at another exercise (temporarily skip some exercises or go back to a previous one)
- `r`: Reset status and file of the selected exercise (you need to _reload/reopen_ its file in your editor afterwards)
See the footer of the list for all possible keys.
## Questions?
If you need any help while doing the exercises and the builtin-hints aren't helpful, feel free to ask in the [_Q&A_ category of the discussions](https://github.com/rust-lang/rustlings/discussions/categories/q-a?discussions_q=) if your question wasn't asked yet 💡
## Third-Party Exercises
Third-party exercises are a set of exercises maintained by the community.
You can use the same `rustlings` program that you installed with `cargo install rustlings` to run them:
- 🇯🇵 [Japanese Rustlings](https://github.com/sotanengel/rustlings-jp)A Japanese translation of the Rustlings exercises.
- 🇨🇳 [Simplified Chinese Rustlings](https://github.com/SandmeyerX/rustlings-zh-cn): A simplified Chinese translation of the Rustlings exercises.
Do you want to create your own set of Rustlings exercises to focus on some specific topic?
Or do you want to translate the original Rustlings exercises?
Then follow the the guide about [third-party exercises](https://github.com/rust-lang/rustlings/blob/main/THIRD_PARTY_EXERCISES.md)!
## Continuing On
Once you've completed Rustlings, put your new knowledge to good use!
Continue practicing your Rust skills by building your own projects, contributing to Rustlings, or finding other open-source projects to contribute to.
## Uninstalling Rustlings
If you want to remove Rustlings from your system, run the following command:
```bash
cargo uninstall rustlings
```
## Contributing
See [CONTRIBUTING.md](https://github.com/rust-lang/rustlings/blob/main/CONTRIBUTING.md) 🔗
## Contributors ✨
Thanks to [all the wonderful contributors](https://github.com/rust-lang/rustlings/graphs/contributors) 🎉

View File

@ -1,53 +0,0 @@
# Third-Party Exercises
The support of Rustlings for third-party exercises allows you to create your own set of Rustlings exercises to focus on some specific topic.
You could also offer a translation of the original Rustlings exercises as third-party exercises.
## Getting started
To create third-party exercises, install Rustlings and run `rustlings dev new PROJECT_NAME`.
This command will, similar to `cargo new PROJECT_NAME`, create a template directory called `PROJECT_NAME` with all what you need to get started.
Read the comments in the generated `info.toml` file to understand its format.
It allows you to set a custom welcome and final message and specify the metadata of every exercise.
## Create an exercise
Here is an example of the metadata of one file:
```toml
[[exercises]]
name = "intro1"
hint = """
To finish this exercise, you need to …
This link might help you …"""
```
After entering this in `info.toml`, create the file `intro1.rs` in the `exercises/` directory.
The exercise needs to contain a `main` function, but it can be empty.
Adding tests is recommended.
Look at the official Rustlings exercises for inspiration.
You can optionally add a solution file `intro1.rs` to the `solutions/` directory.
Now, run `rustlings dev check`.
It will tell you about any issues with your exercises.
For example, it will tell you to run `rustlings dev update` to update the `Cargo.toml` file to include the new exercise `intro1`.
`rustlings dev check` will also run your solutions (if you have any) to make sure that they run successfully.
That's it!
You finished your first exercise 🎉
## Publish
Now, add more exercises and publish them as a Git repository.
Users just have to clone that repository and run `rustlings` in it to start working on your set of exercises just like the official ones.
One difference to the official exercises is that the solution files will not be hidden until the user finishes an exercise.
But you can trust the users to not look at the solution too early 😉
## Share
After publishing your set of exercises, open an issue or a pull request in the official Rustlings repository to link to your project in the README 😃

View File

@ -1,15 +1,11 @@
disallowed-types = [ disallowed-types = [
# Inefficient. Use `.queue(…)` instead. { path = "crossterm::style::Stylize", reason = "inefficient, use `.queue(…)` instead" },
"crossterm::style::Stylize", { path = "crossterm::style::styled_content::StyledContent", reason = "inefficient, use `.queue(…)` instead" },
"crossterm::style::styled_content::StyledContent",
] ]
disallowed-methods = [ disallowed-methods = [
# Inefficient. Use `.queue(…)` instead. { path = "crossterm::style::style", reason = "inefficient, use `.queue(…)` instead" },
"crossterm::style::style", { path = "std::thread::spawn", replacement = "std::thread::Builder::spawn", reason = "handle the error" },
# Use `thread::Builder::spawn` instead and handle the error. { path = "std::thread::Scope::spawn", replacement = "std::thread::Builder::spawn", reason = "handle the error" },
"std::thread::spawn", { path = "std::process::exit", replacement = "std::process::ExitCode" },
"std::thread::Scope::spawn",
# Return `ExitCode` instead.
"std::process::exit",
] ]

View File

@ -1,4 +1,4 @@
# Don't edit the `bin` list manually! It is updated by `cargo run -- dev update`. This comment line will be stripped in `rustlings init`. # Don't edit the `bin` list manually! It is updated by `cargo dev update`. This comment line will be stripped in `rustlings init`.
bin = [ bin = [
{ name = "intro1", path = "../exercises/00_intro/intro1.rs" }, { name = "intro1", path = "../exercises/00_intro/intro1.rs" },
{ name = "intro1_sol", path = "../solutions/00_intro/intro1.rs" }, { name = "intro1_sol", path = "../solutions/00_intro/intro1.rs" },

View File

@ -1,6 +1,6 @@
fn main() { fn main() {
let number = "T-H-R-E-E"; // Don't change this line let number = "T-H-R-E-E"; // Don't change this line
println!("Spell a number: {}", number); println!("Spell a number: {number}");
// TODO: Fix the compiler error by changing the line below without renaming the variable. // TODO: Fix the compiler error by changing the line below without renaming the variable.
number = 3; number = 3;

View File

@ -9,26 +9,6 @@ fn vec_loop(input: &[i32]) -> Vec<i32> {
output output
} }
fn vec_map_example(input: &[i32]) -> Vec<i32> {
// An example of collecting a vector after mapping.
// We map each element of the `input` slice to its value plus 1.
// If the input is `[1, 2, 3]`, the output is `[2, 3, 4]`.
input.iter().map(|element| element + 1).collect()
}
fn vec_map(input: &[i32]) -> Vec<i32> {
// TODO: Here, we also want to multiply each element in the `input` slice
// by 2, but with iterator mapping instead of manually pushing into an empty
// vector.
// See the example in the function `vec_map_example` above.
input
.iter()
.map(|element| {
// ???
})
.collect()
}
fn main() { fn main() {
// You can optionally experiment here. // You can optionally experiment here.
} }
@ -43,18 +23,4 @@ mod tests {
let ans = vec_loop(&input); let ans = vec_loop(&input);
assert_eq!(ans, [4, 8, 12, 16, 20]); assert_eq!(ans, [4, 8, 12, 16, 20]);
} }
#[test]
fn test_vec_map_example() {
let input = [1, 2, 3];
let ans = vec_map_example(&input);
assert_eq!(ans, [2, 3, 4]);
}
#[test]
fn test_vec_map() {
let input = [2, 4, 6, 8, 10];
let ans = vec_map(&input);
assert_eq!(ans, [4, 8, 12, 16, 20]);
}
} }

View File

@ -7,4 +7,4 @@ Useful in combination with enums is Rust's "pattern matching" facility, which ma
## Further information ## Further information
- [Enums](https://doc.rust-lang.org/book/ch06-00-enums.html) - [Enums](https://doc.rust-lang.org/book/ch06-00-enums.html)
- [Pattern syntax](https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html) - [Pattern syntax](https://doc.rust-lang.org/book/ch19-03-pattern-syntax.html)

View File

@ -23,6 +23,7 @@ mod tests {
assert_eq!(trim_me("Hello! "), "Hello!"); assert_eq!(trim_me("Hello! "), "Hello!");
assert_eq!(trim_me(" What's up!"), "What's up!"); assert_eq!(trim_me(" What's up!"), "What's up!");
assert_eq!(trim_me(" Hola! "), "Hola!"); assert_eq!(trim_me(" Hola! "), "Hola!");
assert_eq!(trim_me("Hi!"), "Hi!");
} }
#[test] #[test]

View File

@ -1,8 +1,8 @@
// This function returns how much icecream there is left in the fridge. // This function returns how much ice cream there is left in the fridge.
// If it's before 22:00 (24-hour system), then 5 scoops are left. At 22:00, // If it's before 22:00 (24-hour system), then 5 scoops are left. At 22:00,
// someone eats it all, so no icecream is left (value 0). Return `None` if // someone eats it all, so no ice cream is left (value 0). Return `None` if
// `hour_of_day` is higher than 23. // `hour_of_day` is higher than 23.
fn maybe_icecream(hour_of_day: u16) -> Option<u16> { fn maybe_ice_cream(hour_of_day: u16) -> Option<u16> {
// TODO: Complete the function body. // TODO: Complete the function body.
} }
@ -18,19 +18,19 @@ mod tests {
fn raw_value() { fn raw_value() {
// TODO: Fix this test. How do you get the value contained in the // TODO: Fix this test. How do you get the value contained in the
// Option? // Option?
let icecreams = maybe_icecream(12); let ice_creams = maybe_ice_cream(12);
assert_eq!(icecreams, 5); // Don't change this line. assert_eq!(ice_creams, 5); // Don't change this line.
} }
#[test] #[test]
fn check_icecream() { fn check_ice_cream() {
assert_eq!(maybe_icecream(0), Some(5)); assert_eq!(maybe_ice_cream(0), Some(5));
assert_eq!(maybe_icecream(9), Some(5)); assert_eq!(maybe_ice_cream(9), Some(5));
assert_eq!(maybe_icecream(18), Some(5)); assert_eq!(maybe_ice_cream(18), Some(5));
assert_eq!(maybe_icecream(22), Some(0)); assert_eq!(maybe_ice_cream(22), Some(0));
assert_eq!(maybe_icecream(23), Some(0)); assert_eq!(maybe_ice_cream(23), Some(0));
assert_eq!(maybe_icecream(24), None); assert_eq!(maybe_ice_cream(24), None);
assert_eq!(maybe_icecream(25), None); assert_eq!(maybe_ice_cream(25), None);
} }
} }

View File

@ -10,6 +10,7 @@ struct PositiveNonzeroInteger(u64);
impl PositiveNonzeroInteger { impl PositiveNonzeroInteger {
fn new(value: i64) -> Result<Self, CreationError> { fn new(value: i64) -> Result<Self, CreationError> {
// TODO: This function shouldn't always return an `Ok`. // TODO: This function shouldn't always return an `Ok`.
// Read the tests below to clarify what should be returned.
Ok(Self(value as u64)) Ok(Self(value as u64))
} }
} }

View File

@ -6,7 +6,7 @@
// //
// In short, this particular use case for boxes is for when you want to own a // In short, this particular use case for boxes is for when you want to own a
// value and you care only that it is a type which implements a particular // value and you care only that it is a type which implements a particular
// trait. To do so, The `Box` is declared as of type `Box<dyn Trait>` where // trait. To do so, the `Box` is declared as of type `Box<dyn Trait>` where
// `Trait` is the trait the compiler looks for on any value used in that // `Trait` is the trait the compiler looks for on any value used in that
// context. For this exercise, that context is the potential errors which // context. For this exercise, that context is the potential errors which
// can be returned in a `Result`. // can be returned in a `Result`.

View File

@ -39,6 +39,8 @@ mod tests {
#[test] #[test]
fn test_success() { fn test_success() {
assert_eq!(divide(81, 9), Ok(9)); assert_eq!(divide(81, 9), Ok(9));
assert_eq!(divide(81, -1), Ok(-81));
assert_eq!(divide(i64::MIN, i64::MIN), Ok(1));
} }
#[test] #[test]

View File

@ -10,5 +10,6 @@ of exercises to Rustlings, but is all about learning to write Macros.
## Further information ## Further information
- [Macros](https://doc.rust-lang.org/book/ch19-06-macros.html) - [The Rust Book - Macros](https://doc.rust-lang.org/book/ch20-05-macros.html)
- [The Little Book of Rust Macros](https://veykril.github.io/tlborm/) - [The Little Book of Rust Macros](https://veykril.github.io/tlborm/)
- [Rust by Example - macro_rules!](https://doc.rust-lang.org/rust-by-example/macros.html)

View File

@ -1,7 +1,6 @@
// Here are some more easy Clippy fixes so you can see its utility 📎 // Here are some more easy Clippy fixes so you can see its utility.
// TODO: Fix all the Clippy lints. // TODO: Fix all the Clippy lints.
#[rustfmt::skip]
#[allow(unused_variables, unused_assignments)] #[allow(unused_variables, unused_assignments)]
fn main() { fn main() {
let my_option: Option<&str> = None; let my_option: Option<&str> = None;
@ -11,14 +10,16 @@ fn main() {
println!("{}", my_option.unwrap()); println!("{}", my_option.unwrap());
} }
#[rustfmt::skip]
let my_arr = &[ let my_arr = &[
-1, -2, -3 -1, -2, -3
-4, -5, -6 -4, -5, -6
]; ];
println!("My array! Here it is: {my_arr:?}"); println!("My array! Here it is: {my_arr:?}");
let my_empty_vec = vec![1, 2, 3, 4, 5].resize(0, 5); let mut my_vec = vec![1, 2, 3, 4, 5];
println!("This Vec is empty, see? {my_empty_vec:?}"); my_vec.resize(0, 5);
println!("This Vec is empty, see? {my_vec:?}");
let mut value_a = 45; let mut value_a = 45;
let mut value_b = 66; let mut value_b = 66;

View File

@ -9,7 +9,7 @@
| vecs | §8.1 | | vecs | §8.1 |
| move_semantics | §4.1-2 | | move_semantics | §4.1-2 |
| structs | §5.1, §5.3 | | structs | §5.1, §5.3 |
| enums | §6, §18.3 | | enums | §6, §19.3 |
| strings | §8.2 | | strings | §8.2 |
| modules | §7 | | modules | §7 |
| hashmaps | §8.3 | | hashmaps | §8.3 |
@ -22,6 +22,6 @@
| iterators | §13.2-4 | | iterators | §13.2-4 |
| smart_pointers | §15, §16.3 | | smart_pointers | §15, §16.3 |
| threads | §16.1-3 | | threads | §16.1-3 |
| macros | §19.5 | | macros | §20.5 |
| clippy | §21.4 | | clippy | Appendix D |
| conversions | n/a | | conversions | n/a |

View File

@ -1,13 +0,0 @@
{
"project": {
"homepage": "https://rustlings.cool",
"repository": "https://github.com/rust-lang/rustlings"
},
"marketing": {
"analytics": {
"plausible": {
"domain": "rustlings.cool"
}
}
}
}

View File

@ -9,8 +9,8 @@ cargo upgrades
# Similar to CI # Similar to CI
cargo clippy -- --deny warnings cargo clippy -- --deny warnings
cargo fmt --all --check cargo fmt --all --check
cargo test --workspace --all-targets cargo test --workspace
cargo run -- dev check --require-solutions cargo dev check --require-solutions
# MSRV # MSRV
cargo +1.85 run -- dev check --require-solutions cargo +1.88 dev check --require-solutions

View File

@ -16,9 +16,9 @@ include = [
proc-macro = true proc-macro = true
[dependencies] [dependencies]
quote = "1.0.37" quote = "1.0"
serde.workspace = true serde.workspace = true
toml_edit.workspace = true toml.workspace = true
[lints] [lints]
workspace = true workspace = true

View File

@ -318,16 +318,7 @@ of the Rust book to learn more."""
name = "vecs2" name = "vecs2"
dir = "05_vecs" dir = "05_vecs"
hint = """ hint = """
In the first function, we create an empty vector and want to push new elements Use the `.push()` method on the vector to push new elements to it."""
to it.
In the second function, we map the values of the input and collect them into
a vector.
After you've completed both functions, decide for yourself which approach you
like better.
What do you think is the more commonly used pattern under Rust developers?"""
# MOVE SEMANTICS # MOVE SEMANTICS
@ -764,7 +755,7 @@ Notice how the trait takes ownership of `self` and returns `Self`.
Although the signature of `append_bar` in the trait takes `self` as argument, Although the signature of `append_bar` in the trait takes `self` as argument,
the implementation can take `mut self` instead. This is possible because the the implementation can take `mut self` instead. This is possible because the
the value is owned anyway.""" value is owned anyway."""
[[exercises]] [[exercises]]
name = "traits3" name = "traits3"

View File

@ -16,7 +16,7 @@ struct InfoFile {
#[proc_macro] #[proc_macro]
pub fn include_files(_: TokenStream) -> TokenStream { pub fn include_files(_: TokenStream) -> TokenStream {
let info_file = include_str!("../info.toml"); let info_file = include_str!("../info.toml");
let exercises = toml_edit::de::from_str::<InfoFile>(info_file) let exercises = toml::de::from_str::<InfoFile>(info_file)
.expect("Failed to parse `info.toml`") .expect("Failed to parse `info.toml`")
.exercises; .exercises;

View File

@ -1,6 +1,6 @@
fn main() { fn main() {
let number = "T-H-R-E-E"; let number = "T-H-R-E-E";
println!("Spell a number: {}", number); println!("Spell a number: {number}");
// Using variable shadowing // Using variable shadowing
// https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#shadowing // https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#shadowing

View File

@ -8,22 +8,6 @@ fn vec_loop(input: &[i32]) -> Vec<i32> {
output output
} }
fn vec_map_example(input: &[i32]) -> Vec<i32> {
// An example of collecting a vector after mapping.
// We map each element of the `input` slice to its value plus 1.
// If the input is `[1, 2, 3]`, the output is `[2, 3, 4]`.
input.iter().map(|element| element + 1).collect()
}
fn vec_map(input: &[i32]) -> Vec<i32> {
// We will dive deeper into iterators, but for now, this is all what you
// had to do!
// Advanced note: This method is more efficient because it automatically
// preallocates enough capacity. This can be done manually in `vec_loop`
// using `Vec::with_capacity(input.len())` instead of `Vec::new()`.
input.iter().map(|element| 2 * element).collect()
}
fn main() { fn main() {
// You can optionally experiment here. // You can optionally experiment here.
} }
@ -38,18 +22,4 @@ mod tests {
let ans = vec_loop(&input); let ans = vec_loop(&input);
assert_eq!(ans, [4, 8, 12, 16, 20]); assert_eq!(ans, [4, 8, 12, 16, 20]);
} }
#[test]
fn test_vec_map_example() {
let input = [1, 2, 3];
let ans = vec_map_example(&input);
assert_eq!(ans, [2, 3, 4]);
}
#[test]
fn test_vec_map() {
let input = [2, 4, 6, 8, 10];
let ans = vec_map(&input);
assert_eq!(ans, [4, 8, 12, 16, 20]);
}
} }

View File

@ -4,8 +4,6 @@ fn main() {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
// TODO: Fix the compiler errors only by reordering the lines in the test.
// Don't add, change or remove any line.
#[test] #[test]
fn move_semantics4() { fn move_semantics4() {
let mut x = Vec::new(); let mut x = Vec::new();

View File

@ -26,6 +26,7 @@ mod tests {
assert_eq!(trim_me("Hello! "), "Hello!"); assert_eq!(trim_me("Hello! "), "Hello!");
assert_eq!(trim_me(" What's up!"), "What's up!"); assert_eq!(trim_me(" What's up!"), "What's up!");
assert_eq!(trim_me(" Hola! "), "Hola!"); assert_eq!(trim_me(" Hola! "), "Hola!");
assert_eq!(trim_me("Hi!"), "Hi!");
} }
#[test] #[test]

View File

@ -19,7 +19,7 @@ fn main() {
// `.into()` converts a type into an expected type. // `.into()` converts a type into an expected type.
// If it is called where `String` is expected, it will convert `&str` to `String`. // If it is called where `String` is expected, it will convert `&str` to `String`.
string("nice weather".into()); string("nice weather".into());
// But if it is called where `&str` is expected, then `&str` is kept `&str` since no conversion is needed. // But if it is called where `&str` is expected, then `&str` is kept as `&str` since no conversion is needed.
// If you remove the `#[allow(…)]` line, then Clippy will tell you to remove `.into()` below since it is a useless conversion. // If you remove the `#[allow(…)]` line, then Clippy will tell you to remove `.into()` below since it is a useless conversion.
#[allow(clippy::useless_conversion)] #[allow(clippy::useless_conversion)]
string_slice("nice weather".into()); string_slice("nice weather".into());

View File

@ -1,7 +1,7 @@
// A basket of fruits in the form of a hash map needs to be defined. The key // A basket of fruits in the form of a hash map needs to be defined. The key
// represents the name of the fruit and the value represents how many of that // represents the name of the fruit and the value represents how many of that
// particular fruit is in the basket. You have to put at least 3 different // particular fruit is in the basket. You have to put at least 3 different
// types of fruits (e.g apple, banana, mango) in the basket and the total count // types of fruits (e.g. apple, banana, mango) in the basket and the total count
// of all the fruits should be at least 5. // of all the fruits should be at least 5.
use std::collections::HashMap; use std::collections::HashMap;

View File

@ -1,8 +1,8 @@
// This function returns how much icecream there is left in the fridge. // This function returns how much ice cream there is left in the fridge.
// If it's before 22:00 (24-hour system), then 5 scoops are left. At 22:00, // If it's before 22:00 (24-hour system), then 5 scoops are left. At 22:00,
// someone eats it all, so no icecream is left (value 0). Return `None` if // someone eats it all, so no ice cream is left (value 0). Return `None` if
// `hour_of_day` is higher than 23. // `hour_of_day` is higher than 23.
fn maybe_icecream(hour_of_day: u16) -> Option<u16> { fn maybe_ice_cream(hour_of_day: u16) -> Option<u16> {
match hour_of_day { match hour_of_day {
0..=21 => Some(5), 0..=21 => Some(5),
22..=23 => Some(0), 22..=23 => Some(0),
@ -21,19 +21,19 @@ mod tests {
#[test] #[test]
fn raw_value() { fn raw_value() {
// Using `unwrap` is fine in a test. // Using `unwrap` is fine in a test.
let icecreams = maybe_icecream(12).unwrap(); let ice_creams = maybe_ice_cream(12).unwrap();
assert_eq!(icecreams, 5); assert_eq!(ice_creams, 5);
} }
#[test] #[test]
fn check_icecream() { fn check_ice_cream() {
assert_eq!(maybe_icecream(0), Some(5)); assert_eq!(maybe_ice_cream(0), Some(5));
assert_eq!(maybe_icecream(9), Some(5)); assert_eq!(maybe_ice_cream(9), Some(5));
assert_eq!(maybe_icecream(18), Some(5)); assert_eq!(maybe_ice_cream(18), Some(5));
assert_eq!(maybe_icecream(22), Some(0)); assert_eq!(maybe_ice_cream(22), Some(0));
assert_eq!(maybe_icecream(23), Some(0)); assert_eq!(maybe_ice_cream(23), Some(0));
assert_eq!(maybe_icecream(24), None); assert_eq!(maybe_ice_cream(24), None);
assert_eq!(maybe_icecream(25), None); assert_eq!(maybe_ice_cream(25), None);
} }
} }

View File

@ -6,7 +6,7 @@
// //
// In short, this particular use case for boxes is for when you want to own a // In short, this particular use case for boxes is for when you want to own a
// value and you care only that it is a type which implements a particular // value and you care only that it is a type which implements a particular
// trait. To do so, The `Box` is declared as of type `Box<dyn Trait>` where // trait. To do so, the `Box` is declared as of type `Box<dyn Trait>` where
// `Trait` is the trait the compiler looks for on any value used in that // `Trait` is the trait the compiler looks for on any value used in that
// context. For this exercise, that context is the potential errors which // context. For this exercise, that context is the potential errors which
// can be returned in a `Result`. // can be returned in a `Result`.

View File

@ -4,7 +4,7 @@ fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
fn main() { fn main() {
let string1 = String::from("long string is long"); let string1 = String::from("long string is long");
// Solution1: You can move `strings2` out of the inner block so that it is // Solution 1: You can move `strings2` out of the inner block so that it is
// not dropped before the print statement. // not dropped before the print statement.
let string2 = String::from("xyz"); let string2 = String::from("xyz");
let result; let result;
@ -21,7 +21,7 @@ fn main() {
{ {
let string2 = String::from("xyz"); let string2 = String::from("xyz");
result = longest(&string1, &string2); result = longest(&string1, &string2);
// Solution2: You can move the print statement into the inner block so // Solution 2: You can move the print statement into the inner block so
// that it is executed before `string2` is dropped. // that it is executed before `string2` is dropped.
println!("The longest string is '{result}'"); println!("The longest string is '{result}'");
// `string2` dropped here (end of the inner scope). // `string2` dropped here (end of the inner scope).

View File

@ -52,6 +52,8 @@ mod tests {
#[test] #[test]
fn test_success() { fn test_success() {
assert_eq!(divide(81, 9), Ok(9)); assert_eq!(divide(81, 9), Ok(9));
assert_eq!(divide(81, -1), Ok(-81));
assert_eq!(divide(i64::MIN, i64::MIN), Ok(1));
} }
#[test] #[test]

View File

@ -63,12 +63,10 @@ mod tests {
println!("reference count = {}", Rc::strong_count(&sun)); // 7 references println!("reference count = {}", Rc::strong_count(&sun)); // 7 references
saturn.details(); saturn.details();
// TODO
let uranus = Planet::Uranus(Rc::clone(&sun)); let uranus = Planet::Uranus(Rc::clone(&sun));
println!("reference count = {}", Rc::strong_count(&sun)); // 8 references println!("reference count = {}", Rc::strong_count(&sun)); // 8 references
uranus.details(); uranus.details();
// TODO
let neptune = Planet::Neptune(Rc::clone(&sun)); let neptune = Planet::Neptune(Rc::clone(&sun));
println!("reference count = {}", Rc::strong_count(&sun)); // 9 references println!("reference count = {}", Rc::strong_count(&sun)); // 9 references
neptune.details(); neptune.details();

View File

@ -1,4 +1,4 @@
// Added the attribute `macro_use` attribute. // Added the `macro_use` attribute.
#[macro_use] #[macro_use]
mod macros { mod macros {
macro_rules! my_macro { macro_rules! my_macro {

View File

@ -1,6 +1,5 @@
use std::mem; use std::mem;
#[rustfmt::skip]
#[allow(unused_variables, unused_assignments)] #[allow(unused_variables, unused_assignments)]
fn main() { fn main() {
let my_option: Option<&str> = None; let my_option: Option<&str> = None;
@ -11,21 +10,22 @@ fn main() {
} }
// A comma was missing. // A comma was missing.
#[rustfmt::skip]
let my_arr = &[ let my_arr = &[
-1, -2, -3, -1, -2, -3,
-4, -5, -6, -4, -5, -6,
]; ];
println!("My array! Here it is: {:?}", my_arr); println!("My array! Here it is: {my_arr:?}");
let mut my_empty_vec = vec![1, 2, 3, 4, 5]; let mut my_vec = vec![1, 2, 3, 4, 5];
// `resize` mutates a vector instead of returning a new one. // `resize` mutates a vector instead of returning a new one.
// `resize(0, …)` clears a vector, so it is better to use `clear`. // `resize(0, …)` clears a vector, so it is better to use `clear`.
my_empty_vec.clear(); my_vec.clear();
println!("This Vec is empty, see? {my_empty_vec:?}"); println!("This Vec is empty, see? {my_vec:?}");
let mut value_a = 45; let mut value_a = 45;
let mut value_b = 66; let mut value_b = 66;
// Use `mem::swap` to correctly swap two values. // Use `mem::swap` to correctly swap two values.
mem::swap(&mut value_a, &mut value_b); mem::swap(&mut value_a, &mut value_b);
println!("value a: {}; value b: {}", value_a, value_b); println!("value a: {value_a}; value b: {value_b}");
} }

View File

@ -60,8 +60,7 @@ pub struct AppState {
file_buf: Vec<u8>, file_buf: Vec<u8>,
official_exercises: bool, official_exercises: bool,
cmd_runner: CmdRunner, cmd_runner: CmdRunner,
// Running in VS Code. emit_file_links: bool,
vs_code: bool,
} }
impl AppState { impl AppState {
@ -181,7 +180,8 @@ impl AppState {
file_buf, file_buf,
official_exercises: !Path::new("info.toml").exists(), official_exercises: !Path::new("info.toml").exists(),
cmd_runner, cmd_runner,
vs_code: env::var_os("TERM_PROGRAM").is_some_and(|v| v == "vscode"), // VS Code has its own file link handling
emit_file_links: env::var_os("TERM_PROGRAM").is_none_or(|v| v != "vscode"),
}; };
Ok((slf, state_file_status)) Ok((slf, state_file_status))
@ -218,8 +218,8 @@ impl AppState {
} }
#[inline] #[inline]
pub fn vs_code(&self) -> bool { pub fn emit_file_links(&self) -> bool {
self.vs_code self.emit_file_links
} }
// Write the state file. // Write the state file.
@ -315,7 +315,7 @@ impl AppState {
} }
// Official exercises: Dump the original file from the binary. // Official exercises: Dump the original file from the binary.
// Third-party exercises: Reset the exercise file with `git stash`. // Community exercises: Reset the exercise file with `git stash`.
fn reset(&self, exercise_ind: usize, path: &str) -> Result<()> { fn reset(&self, exercise_ind: usize, path: &str) -> Result<()> {
if self.official_exercises { if self.official_exercises {
return EMBEDDED_FILES return EMBEDDED_FILES
@ -385,7 +385,7 @@ impl AppState {
} }
/// Official exercises: Dump the solution file from the binary and return its path. /// Official exercises: Dump the solution file from the binary and return its path.
/// Third-party exercises: Check if a solution file exists and return its path in that case. /// Community exercises: Check if a solution file exists and return its path in that case.
pub fn current_solution_path(&self) -> Result<Option<String>> { pub fn current_solution_path(&self) -> Result<Option<String>> {
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
return Ok(None); return Ok(None);
@ -621,7 +621,7 @@ mod tests {
file_buf: Vec::new(), file_buf: Vec::new(),
official_exercises: true, official_exercises: true,
cmd_runner: CmdRunner::build().unwrap(), cmd_runner: CmdRunner::build().unwrap(),
vs_code: false, emit_file_links: true,
}; };
let mut assert = |done: [bool; 3], expected: [Option<usize>; 3]| { let mut assert = |done: [bool; 3], expected: [Option<usize>; 3]| {

View File

@ -134,7 +134,14 @@ mod tests {
); );
assert_eq!( assert_eq!(
updated_cargo_toml(&exercise_infos, "abc\nbin = [xxx]\n123", b"../").unwrap(), updated_cargo_toml(
&exercise_infos,
"abc\n\
bin = [xxx]\n\
123",
b"../"
)
.unwrap(),
br#"abc br#"abc
bin = [ bin = [
{ name = "1", path = "../exercises/1.rs" }, { name = "1", path = "../exercises/1.rs" },

View File

@ -1,7 +1,7 @@
use anyhow::{Context, Result, bail}; use anyhow::{Context, Result, bail};
use serde::Deserialize; use serde::Deserialize;
use std::{ use std::{
io::Read, io::{Read, pipe},
path::PathBuf, path::PathBuf,
process::{Command, Stdio}, process::{Command, Stdio},
}; };
@ -17,7 +17,7 @@ fn run_cmd(mut cmd: Command, description: &str, output: Option<&mut Vec<u8>>) ->
}; };
let mut handle = if let Some(output) = output { let mut handle = if let Some(output) = output {
let (mut reader, writer) = os_pipe::pipe().with_context(|| { let (mut reader, writer) = pipe().with_context(|| {
format!("Failed to create a pipe to run the command `{description}``") format!("Failed to create a pipe to run the command `{description}``")
})?; })?;

View File

@ -8,7 +8,7 @@ mod update;
#[derive(Subcommand)] #[derive(Subcommand)]
pub enum DevCommands { pub enum DevCommands {
/// Create a new project for third-party Rustlings exercises /// Create a new project for community exercises
New { New {
/// The path to create the project in /// The path to create the project in
path: PathBuf, path: PathBuf,

View File

@ -15,6 +15,7 @@ use crate::{
cmd::CmdRunner, cmd::CmdRunner,
exercise::{OUTPUT_CAPACITY, RunnableExercise}, exercise::{OUTPUT_CAPACITY, RunnableExercise},
info_file::{ExerciseInfo, InfoFile}, info_file::{ExerciseInfo, InfoFile},
term::ProgressCounter,
}; };
const MAX_N_EXERCISES: usize = 999; const MAX_N_EXERCISES: usize = 999;
@ -43,7 +44,7 @@ fn check_cargo_toml(
if old_bins != new_bins { if old_bins != new_bins {
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
bail!( bail!(
"The file `dev/Cargo.toml` is outdated. Run `cargo run -- dev update` to update it. Then run `cargo run -- dev check` again" "The file `dev/Cargo.toml` is outdated. Run `cargo dev update` to update it. Then run `cargo run -- dev check` again"
); );
} }
@ -105,13 +106,15 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
if !file_buf.contains("fn main()") { if !file_buf.contains("fn main()") {
bail!( bail!(
"The `main` function is missing in the file `{path}`.\nCreate at least an empty `main` function to avoid language server errors" "The `main` function is missing in the file `{path}`.\n\
Create at least an empty `main` function to avoid language server errors"
); );
} }
if !file_buf.contains("// TODO") { if !file_buf.contains("// TODO") {
bail!( bail!(
"Didn't find any `// TODO` comment in the file `{path}`.\nYou need to have at least one such comment to guide the user." "Didn't find any `// TODO` comment in the file `{path}`.\n\
You need to have at least one such comment to guide the user."
); );
} }
@ -217,10 +220,7 @@ fn check_exercises_unsolved(
.collect::<Result<Vec<_>, _>>() .collect::<Result<Vec<_>, _>>()
.context("Failed to spawn a thread to check if an exercise is already solved")?; .context("Failed to spawn a thread to check if an exercise is already solved")?;
let n_handles = handles.len(); let mut progress_counter = ProgressCounter::new(&mut stdout, handles.len())?;
write!(stdout, "Progress: 0/{n_handles}")?;
stdout.flush()?;
let mut handle_num = 1;
for (exercise_name, handle) in handles { for (exercise_name, handle) in handles {
let Ok(result) = handle.join() else { let Ok(result) = handle.join() else {
@ -229,17 +229,17 @@ fn check_exercises_unsolved(
match result { match result {
Ok(true) => { Ok(true) => {
bail!("The exercise {exercise_name} is already solved.\n{SKIP_CHECK_UNSOLVED_HINT}",) bail!(
"The exercise {exercise_name} is already solved.\n\
{SKIP_CHECK_UNSOLVED_HINT}",
)
} }
Ok(false) => (), Ok(false) => (),
Err(e) => return Err(e), Err(e) => return Err(e),
} }
write!(stdout, "\rProgress: {handle_num}/{n_handles}")?; progress_counter.increment()?;
stdout.flush()?;
handle_num += 1;
} }
stdout.write_all(b"\n")?;
Ok(()) Ok(())
} }
@ -247,10 +247,12 @@ fn check_exercises_unsolved(
fn check_exercises(info_file: &'static InfoFile, cmd_runner: &'static CmdRunner) -> Result<()> { fn check_exercises(info_file: &'static InfoFile, cmd_runner: &'static CmdRunner) -> Result<()> {
match info_file.format_version.cmp(&CURRENT_FORMAT_VERSION) { match info_file.format_version.cmp(&CURRENT_FORMAT_VERSION) {
Ordering::Less => bail!( Ordering::Less => bail!(
"`format_version` < {CURRENT_FORMAT_VERSION} (supported version)\nPlease migrate to the latest format version" "`format_version` < {CURRENT_FORMAT_VERSION} (supported version)\n\
Please migrate to the latest format version"
), ),
Ordering::Greater => bail!( Ordering::Greater => bail!(
"`format_version` > {CURRENT_FORMAT_VERSION} (supported version)\nTry updating the Rustlings program" "`format_version` > {CURRENT_FORMAT_VERSION} (supported version)\n\
Try updating the Rustlings program"
), ),
Ordering::Equal => (), Ordering::Equal => (),
} }
@ -318,10 +320,7 @@ fn check_solutions(
.arg("always") .arg("always")
.stdin(Stdio::null()); .stdin(Stdio::null());
let n_handles = handles.len(); let mut progress_counter = ProgressCounter::new(&mut stdout, handles.len())?;
write!(stdout, "Progress: 0/{n_handles}")?;
stdout.flush()?;
let mut handle_num = 1;
for (exercise_info, handle) in info_file.exercises.iter().zip(handles) { for (exercise_info, handle) in info_file.exercises.iter().zip(handles) {
let Ok(check_result) = handle.join() else { let Ok(check_result) = handle.join() else {
@ -338,7 +337,7 @@ fn check_solutions(
} }
SolutionCheck::MissingOptional => (), SolutionCheck::MissingOptional => (),
SolutionCheck::RunFailure { output } => { SolutionCheck::RunFailure { output } => {
stdout.write_all(b"\n\n")?; drop(progress_counter);
stdout.write_all(&output)?; stdout.write_all(&output)?;
bail!( bail!(
"Running the solution of the exercise {} failed with the error above", "Running the solution of the exercise {} failed with the error above",
@ -348,19 +347,18 @@ fn check_solutions(
SolutionCheck::Err(e) => return Err(e), SolutionCheck::Err(e) => return Err(e),
} }
write!(stdout, "\rProgress: {handle_num}/{n_handles}")?; progress_counter.increment()?;
stdout.flush()?;
handle_num += 1;
} }
stdout.write_all(b"\n")?;
let n_solutions = sol_paths.len();
let handle = thread::Builder::new() let handle = thread::Builder::new()
.spawn(move || check_unexpected_files("solutions", &sol_paths)) .spawn(move || check_unexpected_files("solutions", &sol_paths))
.context( .context(
"Failed to spawn a thread to check for unexpected files in the solutions directory", "Failed to spawn a thread to check for unexpected files in the solutions directory",
)?; )?;
if !fmt_cmd if n_solutions > 0
&& !fmt_cmd
.status() .status()
.context("Failed to run `rustfmt` on all solution files")? .context("Failed to run `rustfmt` on all solution files")?
.success() .success()
@ -379,7 +377,7 @@ pub fn check(require_solutions: bool) -> Result<()> {
} }
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
// A hack to make `cargo run -- dev check` work when developing Rustlings. // A hack to make `cargo dev check` work when developing Rustlings.
check_cargo_toml(&info_file.exercises, "dev/Cargo.toml", b"../")?; check_cargo_toml(&info_file.exercises, "dev/Cargo.toml", b"../")?;
} else { } else {
check_cargo_toml(&info_file.exercises, "Cargo.toml", b"")?; check_cargo_toml(&info_file.exercises, "Cargo.toml", b"")?;

View File

@ -78,18 +78,17 @@ pub fn new(path: &Path, no_git: bool) -> Result<()> {
Ok(()) Ok(())
} }
pub const GITIGNORE: &[u8] = b".rustlings-state.txt pub const GITIGNORE: &[u8] = b"Cargo.lock
Cargo.lock
target/ target/
.vscode/ .vscode/
!.vscode/extensions.json !.vscode/extensions.json
"; ";
const INFO_FILE_BEFORE_FORMAT_VERSION: &str = const INFO_FILE_BEFORE_FORMAT_VERSION: &str =
"# The format version is an indicator of the compatibility of third-party exercises with the "# The format version is an indicator of the compatibility of community exercises with the
# Rustlings program. # Rustlings program.
# The format version is not the same as the version of the Rustlings program. # The format version is not the same as the version of the Rustlings program.
# In case Rustlings makes an unavoidable breaking change to the expected format of third-party # In case Rustlings makes an unavoidable breaking change to the expected format of community
# exercises, you would need to raise this version and adapt to the new format. # exercises, you would need to raise this version and adapt to the new format.
# Otherwise, the newest version of the Rustlings program won't be able to run these exercises. # Otherwise, the newest version of the Rustlings program won't be able to run these exercises.
format_version = "; format_version = ";
@ -97,7 +96,7 @@ format_version = ";
const INFO_FILE_AFTER_FORMAT_VERSION: &str = r#" const INFO_FILE_AFTER_FORMAT_VERSION: &str = r#"
# Optional multi-line message to be shown to users when just starting with the exercises. # Optional multi-line message to be shown to users when just starting with the exercises.
welcome_message = """Welcome to these third-party Rustlings exercises.""" welcome_message = """Welcome to these community Rustlings exercises."""
# Optional multi-line message to be shown to users after finishing all exercises. # Optional multi-line message to be shown to users after finishing all exercises.
final_message = """We hope that you found the exercises helpful :D""" final_message = """We hope that you found the exercises helpful :D"""
@ -141,7 +140,7 @@ publish = false
const README: &str = "# Rustlings 🦀 const README: &str = "# Rustlings 🦀
Welcome to these third-party Rustlings exercises 😃 Welcome to these community Rustlings exercises 😃
First, [install Rustlings using the official instructions](https://github.com/rust-lang/rustlings) ✅ First, [install Rustlings using the official instructions](https://github.com/rust-lang/rustlings) ✅

View File

@ -28,7 +28,7 @@ pub fn update() -> Result<()> {
let info_file = InfoFile::parse()?; let info_file = InfoFile::parse()?;
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
// A hack to make `cargo run -- dev update` work when developing Rustlings. // A hack to make `cargo dev update` work when developing Rustlings.
update_cargo_toml(&info_file.exercises, "dev/Cargo.toml", b"../") update_cargo_toml(&info_file.exercises, "dev/Cargo.toml", b"../")
.context("Failed to update the file `dev/Cargo.toml`")?; .context("Failed to update the file `dev/Cargo.toml`")?;

View File

@ -20,11 +20,11 @@ struct ExerciseFiles {
} }
fn create_dir_if_not_exists(path: &str) -> Result<()> { fn create_dir_if_not_exists(path: &str) -> Result<()> {
if let Err(e) = create_dir(path) { if let Err(e) = create_dir(path)
if e.kind() != io::ErrorKind::AlreadyExists { && e.kind() != io::ErrorKind::AlreadyExists
{
return Err(Error::from(e).context(format!("Failed to create the directory {path}"))); return Err(Error::from(e).context(format!("Failed to create the directory {path}")));
} }
}
Ok(()) Ok(())
} }
@ -152,7 +152,7 @@ mod tests {
#[test] #[test]
fn dirs() { fn dirs() {
let exercises = toml_edit::de::from_str::<InfoFile>(EMBEDDED_FILES.info_file) let exercises = toml::de::from_str::<InfoFile>(EMBEDDED_FILES.info_file)
.expect("Failed to parse `info.toml`") .expect("Failed to parse `info.toml`")
.exercises; .exercises;

View File

@ -7,22 +7,28 @@ use std::io::{self, StdoutLock, Write};
use crate::{ use crate::{
cmd::CmdRunner, cmd::CmdRunner,
term::{self, CountedWrite, terminal_file_link, write_ansi}, term::{self, CountedWrite, file_path, terminal_file_link, write_ansi},
}; };
/// The initial capacity of the output buffer. /// The initial capacity of the output buffer.
pub const OUTPUT_CAPACITY: usize = 1 << 14; pub const OUTPUT_CAPACITY: usize = 1 << 14;
pub fn solution_link_line(stdout: &mut StdoutLock, solution_path: &str) -> io::Result<()> { pub fn solution_link_line(
stdout: &mut StdoutLock,
solution_path: &str,
emit_file_links: bool,
) -> io::Result<()> {
stdout.queue(SetAttribute(Attribute::Bold))?; stdout.queue(SetAttribute(Attribute::Bold))?;
stdout.write_all(b"Solution")?; stdout.write_all(b"Solution")?;
stdout.queue(ResetColor)?; stdout.queue(ResetColor)?;
stdout.write_all(b" for comparison: ")?; stdout.write_all(b" for comparison: ")?;
if let Some(canonical_path) = term::canonicalize(solution_path) { file_path(stdout, Color::Cyan, |writer| {
terminal_file_link(stdout, solution_path, &canonical_path, Color::Cyan)?; if emit_file_links && let Some(canonical_path) = term::canonicalize(solution_path) {
terminal_file_link(writer, solution_path, &canonical_path)
} else { } else {
stdout.write_all(solution_path.as_bytes())?; writer.stdout().write_all(solution_path.as_bytes())
} }
})?;
stdout.write_all(b"\n") stdout.write_all(b"\n")
} }
@ -42,8 +48,9 @@ fn run_bin(
let success = cmd_runner.run_debug_bin(bin_name, output.as_deref_mut())?; let success = cmd_runner.run_debug_bin(bin_name, output.as_deref_mut())?;
if let Some(output) = output { if let Some(output) = output
if !success { && !success
{
// This output is important to show the user that something went wrong. // This output is important to show the user that something went wrong.
// Otherwise, calling something like `exit(1)` in an exercise without further output // Otherwise, calling something like `exit(1)` in an exercise without further output
// leaves the user confused about why the exercise isn't done yet. // leaves the user confused about why the exercise isn't done yet.
@ -53,7 +60,6 @@ fn run_bin(
write_ansi(output, ResetColor); write_ansi(output, ResetColor);
output.push(b'\n'); output.push(b'\n');
} }
}
Ok(success) Ok(success)
} }
@ -72,13 +78,19 @@ pub struct Exercise {
} }
impl Exercise { impl Exercise {
pub fn terminal_file_link<'a>(&self, writer: &mut impl CountedWrite<'a>) -> io::Result<()> { pub fn terminal_file_link<'a>(
if let Some(canonical_path) = self.canonical_path.as_deref() { &self,
return terminal_file_link(writer, self.path, canonical_path, Color::Blue); writer: &mut impl CountedWrite<'a>,
} emit_file_links: bool,
) -> io::Result<()> {
file_path(writer, Color::Blue, |writer| {
if emit_file_links && let Some(canonical_path) = self.canonical_path.as_deref() {
terminal_file_link(writer, self.path, canonical_path)
} else {
writer.write_str(self.path) writer.write_str(self.path)
} }
})
}
} }
pub trait RunnableExercise { pub trait RunnableExercise {

View File

@ -79,7 +79,7 @@ impl RunnableExercise for ExerciseInfo {
/// The deserialized `info.toml` file. /// The deserialized `info.toml` file.
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct InfoFile { pub struct InfoFile {
/// For possible breaking changes in the future for third-party exercises. /// For possible breaking changes in the future for community exercises.
pub format_version: u8, pub format_version: u8,
/// Shown to users when starting with the exercises. /// Shown to users when starting with the exercises.
pub welcome_message: Option<String>, pub welcome_message: Option<String>,
@ -91,15 +91,15 @@ pub struct InfoFile {
impl InfoFile { impl InfoFile {
/// Official exercises: Parse the embedded `info.toml` file. /// Official exercises: Parse the embedded `info.toml` file.
/// Third-party exercises: Parse the `info.toml` file in the current directory. /// Community exercises: Parse the `info.toml` file in the current directory.
pub fn parse() -> Result<Self> { pub fn parse() -> Result<Self> {
// Read a local `info.toml` if it exists. // Read a local `info.toml` if it exists.
let slf = match fs::read_to_string("info.toml") { let slf = match fs::read_to_string("info.toml") {
Ok(file_content) => toml_edit::de::from_str::<Self>(&file_content) Ok(file_content) => toml::de::from_str::<Self>(&file_content)
.context("Failed to parse the `info.toml` file")?, .context("Failed to parse the `info.toml` file")?,
Err(e) => { Err(e) => {
if e.kind() == ErrorKind::NotFound { if e.kind() == ErrorKind::NotFound {
return toml_edit::de::from_str(EMBEDDED_FILES.info_file) return toml::de::from_str(EMBEDDED_FILES.info_file)
.context("Failed to parse the embedded `info.toml` file"); .context("Failed to parse the embedded `info.toml` file");
} }

View File

@ -35,7 +35,27 @@ pub fn init() -> Result<()> {
.stdin(Stdio::null()) .stdin(Stdio::null())
.stderr(Stdio::null()) .stderr(Stdio::null())
.output() .output()
.context(CARGO_LOCATE_PROJECT_ERR)?; .context(
"Failed to run the command `cargo locate-project …`\n\
Did you already install Rust?\n\
Try running `cargo --version` to diagnose the problem.",
)?;
if !Command::new("cargo")
.arg("clippy")
.arg("--version")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.context("Failed to run the command `cargo clippy --version`")?
.success()
{
bail!(
"Clippy, the official Rust linter, is missing.\n\
Please install it first before initializing Rustlings."
)
}
let mut stdout = io::stdout().lock(); let mut stdout = io::stdout().lock();
let mut init_git = true; let mut init_git = true;
@ -54,15 +74,17 @@ pub fn init() -> Result<()> {
let workspace_manifest_content = fs::read_to_string(&workspace_manifest) let workspace_manifest_content = fs::read_to_string(&workspace_manifest)
.with_context(|| format!("Failed to read the file {}", workspace_manifest.display()))?; .with_context(|| format!("Failed to read the file {}", workspace_manifest.display()))?;
if !workspace_manifest_content.contains("[workspace]\n") if !workspace_manifest_content.contains("[workspace]")
&& !workspace_manifest_content.contains("workspace.") && !workspace_manifest_content.contains("workspace.")
{ {
bail!( bail!(
"The current directory is already part of a Cargo project.\nPlease initialize Rustlings in a different directory" "The current directory is already part of a Cargo project.\n\
Please initialize Rustlings in a different directory"
); );
} }
stdout.write_all(b"This command will create the directory `rustlings/` as a member of this Cargo workspace.\nPress ENTER to continue ")?; stdout.write_all(b"This command will create the directory `rustlings/` as a member of this Cargo workspace.\n\
Press ENTER to continue ")?;
press_enter_prompt(&mut stdout)?; press_enter_prompt(&mut stdout)?;
// Make sure "rustlings" is added to `workspace.members` by making // Make sure "rustlings" is added to `workspace.members` by making
@ -78,7 +100,8 @@ pub fn init() -> Result<()> {
.status()?; .status()?;
if !status.success() { if !status.success() {
bail!( bail!(
"Failed to initialize a new Cargo workspace member.\nPlease initialize Rustlings in a different directory" "Failed to initialize a new Cargo workspace member.\n\
Please initialize Rustlings in a different directory"
); );
} }
@ -87,7 +110,8 @@ pub fn init() -> Result<()> {
.context("Failed to remove the temporary directory `rustlings/`")?; .context("Failed to remove the temporary directory `rustlings/`")?;
init_git = false; init_git = false;
} else { } else {
stdout.write_all(b"This command will create the directory `rustlings/` which will contain the exercises.\nPress ENTER to continue ")?; stdout.write_all(b"This command will create the directory `rustlings/` which will contain the exercises.\n\
Press ENTER to continue ")?;
press_enter_prompt(&mut stdout)?; press_enter_prompt(&mut stdout)?;
} }
@ -166,10 +190,6 @@ pub fn init() -> Result<()> {
Ok(()) Ok(())
} }
const CARGO_LOCATE_PROJECT_ERR: &str = "Failed to run the command `cargo locate-project …`
Did you already install Rust?
Try running `cargo --version` to diagnose the problem.";
const INIT_SOLUTION_FILE: &[u8] = b"fn main() { const INIT_SOLUTION_FILE: &[u8] = b"fn main() {
// DON'T EDIT THIS SOLUTION FILE! // DON'T EDIT THIS SOLUTION FILE!
// It will be automatically filled after you finish the exercise. // It will be automatically filled after you finish the exercise.
@ -178,6 +198,7 @@ const INIT_SOLUTION_FILE: &[u8] = b"fn main() {
pub const RUST_ANALYZER_TOML: &[u8] = br#"check.command = "clippy" pub const RUST_ANALYZER_TOML: &[u8] = br#"check.command = "clippy"
check.extraArgs = ["--profile", "test"] check.extraArgs = ["--profile", "test"]
cargo.targetDir = true
"#; "#;
const GITIGNORE: &[u8] = b"Cargo.lock const GITIGNORE: &[u8] = b"Cargo.lock

View File

@ -118,8 +118,8 @@ impl<'a> ListState<'a> {
} }
fn draw_exercise_name(&self, writer: &mut MaxLenWriter, exercise: &Exercise) -> io::Result<()> { fn draw_exercise_name(&self, writer: &mut MaxLenWriter, exercise: &Exercise) -> io::Result<()> {
if !self.search_query.is_empty() { if !self.search_query.is_empty()
if let Some((pre_highlight, highlight, post_highlight)) = exercise && let Some((pre_highlight, highlight, post_highlight)) = exercise
.name .name
.find(&self.search_query) .find(&self.search_query)
.and_then(|ind| exercise.name.split_at_checked(ind)) .and_then(|ind| exercise.name.split_at_checked(ind))
@ -134,7 +134,6 @@ impl<'a> ListState<'a> {
writer.stdout.queue(SetForegroundColor(Color::Reset))?; writer.stdout.queue(SetForegroundColor(Color::Reset))?;
return writer.write_str(post_highlight); return writer.write_str(post_highlight);
} }
}
writer.write_str(exercise.name) writer.write_str(exercise.name)
} }
@ -186,13 +185,7 @@ impl<'a> ListState<'a> {
writer.write_ascii(&self.name_col_padding[exercise.name.len()..])?; writer.write_ascii(&self.name_col_padding[exercise.name.len()..])?;
// The list links aren't shown correctly in VS Code on Windows. exercise.terminal_file_link(&mut writer, self.app_state.emit_file_links())?;
// But VS Code shows its own links anyway.
if self.app_state.vs_code() {
writer.write_str(exercise.path)?;
} else {
exercise.terminal_file_link(&mut writer)?;
}
writer.write_ascii(&self.path_col_padding[exercise.path.len()..])?; writer.write_ascii(&self.path_col_padding[exercise.path.len()..])?;

View File

@ -58,7 +58,7 @@ enum Subcommands {
/// The name of the exercise /// The name of the exercise
name: Option<String>, name: Option<String>,
}, },
/// Commands for developing (third-party) Rustlings exercises /// Commands for developing (community) Rustlings exercises
#[command(subcommand)] #[command(subcommand)]
Dev(DevCommands), Dev(DevCommands),
} }
@ -104,7 +104,11 @@ fn main() -> Result<ExitCode> {
clear_terminal(&mut stdout)?; clear_terminal(&mut stdout)?;
let welcome_message = welcome_message.trim_ascii(); let welcome_message = welcome_message.trim_ascii();
write!(stdout, "{welcome_message}\n\nPress ENTER to continue ")?; write!(
stdout,
"{welcome_message}\n\n\
Press ENTER to continue "
)?;
press_enter_prompt(&mut stdout)?; press_enter_prompt(&mut stdout)?;
clear_terminal(&mut stdout)?; clear_terminal(&mut stdout)?;
// Flush to be able to show errors occurring before printing a newline to stdout. // Flush to be able to show errors occurring before printing a newline to stdout.
@ -163,7 +167,7 @@ fn main() -> Result<ExitCode> {
} }
app_state app_state
.current_exercise() .current_exercise()
.terminal_file_link(&mut stdout)?; .terminal_file_link(&mut stdout, app_state.emit_file_links())?;
stdout.write_all(b"\n")?; stdout.write_all(b"\n")?;
return Ok(ExitCode::FAILURE); return Ok(ExitCode::FAILURE);

View File

@ -27,7 +27,7 @@ pub fn run(app_state: &mut AppState) -> Result<ExitCode> {
stdout.write_all(b"Ran ")?; stdout.write_all(b"Ran ")?;
app_state app_state
.current_exercise() .current_exercise()
.terminal_file_link(&mut stdout)?; .terminal_file_link(&mut stdout, app_state.emit_file_links())?;
stdout.write_all(b" with errors\n")?; stdout.write_all(b" with errors\n")?;
return Ok(ExitCode::FAILURE); return Ok(ExitCode::FAILURE);
@ -41,7 +41,7 @@ pub fn run(app_state: &mut AppState) -> Result<ExitCode> {
if let Some(solution_path) = app_state.current_solution_path()? { if let Some(solution_path) = app_state.current_solution_path()? {
stdout.write_all(b"\n")?; stdout.write_all(b"\n")?;
solution_link_line(&mut stdout, &solution_path)?; solution_link_line(&mut stdout, &solution_path, app_state.emit_file_links())?;
stdout.write_all(b"\n")?; stdout.write_all(b"\n")?;
} }
@ -50,7 +50,7 @@ pub fn run(app_state: &mut AppState) -> Result<ExitCode> {
stdout.write_all(b"Next exercise: ")?; stdout.write_all(b"Next exercise: ")?;
app_state app_state
.current_exercise() .current_exercise()
.terminal_file_link(&mut stdout)?; .terminal_file_link(&mut stdout, app_state.emit_file_links())?;
stdout.write_all(b"\n")?; stdout.write_all(b"\n")?;
} }
ExercisesProgress::AllDone => (), ExercisesProgress::AllDone => (),

View File

@ -160,6 +160,37 @@ impl<'a, 'lock> CheckProgressVisualizer<'a, 'lock> {
} }
} }
pub struct ProgressCounter<'a, 'lock> {
stdout: &'a mut StdoutLock<'lock>,
total: usize,
counter: usize,
}
impl<'a, 'lock> ProgressCounter<'a, 'lock> {
pub fn new(stdout: &'a mut StdoutLock<'lock>, total: usize) -> io::Result<Self> {
write!(stdout, "Progress: 0/{total}")?;
stdout.flush()?;
Ok(Self {
stdout,
total,
counter: 0,
})
}
pub fn increment(&mut self) -> io::Result<()> {
self.counter += 1;
write!(self.stdout, "\rProgress: {}/{}", self.counter, self.total)?;
self.stdout.flush()
}
}
impl Drop for ProgressCounter<'_, '_> {
fn drop(&mut self) {
let _ = self.stdout.write_all(b"\n\n");
}
}
pub fn progress_bar<'a>( pub fn progress_bar<'a>(
writer: &mut impl CountedWrite<'a>, writer: &mut impl CountedWrite<'a>,
progress: u16, progress: u16,
@ -241,22 +272,18 @@ pub fn canonicalize(path: &str) -> Option<String> {
}) })
} }
pub fn terminal_file_link<'a>( pub fn file_path<'a, W: CountedWrite<'a>>(
writer: &mut impl CountedWrite<'a>, writer: &mut W,
path: &str,
canonical_path: &str,
color: Color, color: Color,
f: impl FnOnce(&mut W) -> io::Result<()>,
) -> io::Result<()> { ) -> io::Result<()> {
writer writer
.stdout() .stdout()
.queue(SetForegroundColor(color))? .queue(SetForegroundColor(color))?
.queue(SetAttribute(Attribute::Underlined))?; .queue(SetAttribute(Attribute::Underlined))?;
writer.stdout().write_all(b"\x1b]8;;file://")?;
writer.stdout().write_all(canonical_path.as_bytes())?; f(writer)?;
writer.stdout().write_all(b"\x1b\\")?;
// Only this part is visible.
writer.write_str(path)?;
writer.stdout().write_all(b"\x1b]8;;\x1b\\")?;
writer writer
.stdout() .stdout()
.queue(SetForegroundColor(Color::Reset))? .queue(SetForegroundColor(Color::Reset))?
@ -265,6 +292,19 @@ pub fn terminal_file_link<'a>(
Ok(()) Ok(())
} }
pub fn terminal_file_link<'a>(
writer: &mut impl CountedWrite<'a>,
path: &str,
canonical_path: &str,
) -> io::Result<()> {
writer.stdout().write_all(b"\x1b]8;;file://")?;
writer.stdout().write_all(canonical_path.as_bytes())?;
writer.stdout().write_all(b"\x1b\\")?;
// Only this part is visible.
writer.write_str(path)?;
writer.stdout().write_all(b"\x1b]8;;\x1b\\")
}
pub fn write_ansi(output: &mut Vec<u8>, command: impl Command) { pub fn write_ansi(output: &mut Vec<u8>, command: impl Command) {
struct FmtWriter<'a>(&'a mut Vec<u8>); struct FmtWriter<'a>(&'a mut Vec<u8>);

View File

@ -233,7 +233,7 @@ impl<'a> WatchState<'a> {
stdout.write_all(b"\n")?; stdout.write_all(b"\n")?;
if let DoneStatus::DoneWithSolution(solution_path) = &self.done_status { if let DoneStatus::DoneWithSolution(solution_path) = &self.done_status {
solution_link_line(stdout, solution_path)?; solution_link_line(stdout, solution_path, self.app_state.emit_file_links())?;
} }
stdout.write_all( stdout.write_all(
@ -252,7 +252,7 @@ impl<'a> WatchState<'a> {
stdout.write_all(b"\nCurrent exercise: ")?; stdout.write_all(b"\nCurrent exercise: ")?;
self.app_state self.app_state
.current_exercise() .current_exercise()
.terminal_file_link(stdout)?; .terminal_file_link(stdout, self.app_state.emit_file_links())?;
stdout.write_all(b"\n\n")?; stdout.write_all(b"\n\n")?;
self.show_prompt(stdout)?; self.show_prompt(stdout)?;

7
website/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
/node_modules/
/package-lock.json
/public/
/static/main.css
/static/processed_images/

41
website/config.toml Normal file
View File

@ -0,0 +1,41 @@
base_url = "https://rustlings.rust-lang.org"
title = "Rustlings"
description = "Small exercises to get you used to reading and writing Rust code!"
compile_sass = false
build_search_index = false
[markdown]
highlight_code = true
highlight_theme = "dracula"
insert_anchor_links = "heading"
[extra]
logo_path = "images/happy_ferris.svg"
[[extra.menu_items]]
name = "Rustlings"
url = "@/_index.md"
[[extra.menu_items]]
name = "Setup"
url = "@/setup/index.md"
[[extra.menu_items]]
name = "Usage"
url = "@/usage/index.md"
[[extra.menu_items]]
name = "Community Exercises"
url = "@/community-exercises/index.md"
[[extra.menu_items]]
name = "Q&A"
url = "https://github.com/rust-lang/rustlings/discussions/categories/q-a?discussions_q="
[[extra.footer_items]]
name = "Repository"
url = "https://github.com/rust-lang/rustlings"
[[extra.footer_items]]
name = "Changelog"
url = "https://github.com/rust-lang/rustlings/blob/main/CHANGELOG.md"
[[extra.footer_items]]
name = "MIT License"
url = "https://github.com/rust-lang/rustlings/blob/main/LICENSE"

21
website/content/_index.md Normal file
View File

@ -0,0 +1,21 @@
+++
+++
Small exercises to get you used to reading and writing [Rust](https://www.rust-lang.org) code - _Recommended in parallel to reading [the official Rust book](https://doc.rust-lang.org/book) 📚_
<script src="https://asciinema.org/a/719805.js" id="asciicast-719805" async="true"></script>
## Quick start
```bash
# Installation
cargo install rustlings
# Initialization
rustlings init
# Moving into new directory
cd rustlings
# Starting Rustlings
rustlings
```
Visit the [**setup**](@/setup/index.md) page for more details 🧰

View File

@ -0,0 +1,73 @@
+++
title = "Community Exercises"
+++
## List of Community Exercises
- 🇯🇵 [Japanese Rustlings](https://github.com/sotanengel/rustlings-jp)A Japanese translation of the Rustlings exercises.
- 🇨🇳 [Simplified Chinese Rustlings](https://github.com/SandmeyerX/rustlings-zh-cn): A simplified Chinese translation of the Rustlings exercises.
> You can use the same `rustlings` program that you installed with `cargo install rustlings` to run community exercises.
## Creating Community Exercises
Rustling's support for community exercises allows you to create your own exercises to focus on some specific topic.
You could also offer a translation of the original Rustlings exercises as community exercises.
### Getting Started
To create community exercises, install Rustlings and run `rustlings dev new PROJECT_NAME`.
This command will, similar to `cargo new PROJECT_NAME`, create the template directory `PROJECT_NAME` with all what you need to get started.
_Read the comments_ in the generated `info.toml` file to understand its format.
It allows you to set a custom welcome and final message and specify the metadata of every exercise.
### Creating an Exercise
Here is an example of the metadata of one exercise:
```toml
[[exercises]]
name = "intro1"
hint = """
To finish this exercise, you need to …
These links might help you …"""
```
After entering this in `info.toml`, create the file `intro1.rs` in the `exercises/` directory.
The exercise needs to contain a `main` function, but it can be empty.
Adding tests is recommended.
Look at the official Rustlings exercises for inspiration.
You can optionally add a solution file `intro1.rs` to the `solutions/` directory.
Now, run `rustlings dev check`.
It will tell you about any issues with your exercises.
For example, it will tell you to run `rustlings dev update` to update the `Cargo.toml` file to include the new exercise `intro1`.
`rustlings dev check` will also run your solutions (if you have any) to make sure that they run successfully.
That's it!
You finished your first exercise 🎉
### Cargo.toml
Except of the `bin` list, you can modify the `Cargo.toml` file as you want.
> The `bin` list is automatically updated by running `rustlings dev update`
- You can add dependencies in the `[dependencies]` table.
- You might want to [configure some lints](https://doc.rust-lang.org/cargo/reference/manifest.html#the-lints-section) for all exercises. You can do so in the `[lints.rust]` and `[lints.clippy]` tables.
### Publishing
Now, add more exercises and publish them as a Git repository.
Users just have to clone that repository and run `rustlings` in it to start working on your exercises (just like the official ones).
One difference to the official exercises is that the solution files will not be hidden until the user finishes an exercise.
But you can trust your users to not open the solution too early 😉
### Sharing
After publishing your community exercises, open an issue or a pull request in the [official Rustlings repository](https://github.com/rust-lang/rustlings) to add your project to the [list of community exercises](#list-of-community-exercises) 😃

View File

@ -0,0 +1,78 @@
+++
title = "Setup"
+++
<!-- toc -->
## Installing Rust
Before installing Rustlings, you must have the **latest version of Rust** installed.
Visit [www.rust-lang.org/tools/install](https://www.rust-lang.org/tools/install) for further instructions.
This will also install _Cargo_, Rust's package/project manager.
> 🐧 If you are on **Linux**, make sure you have `gcc` installed (_for a linker_).
>
> Debian: `sudo apt install gcc`\
> Fedora: `sudo dnf install gcc`
> 🍎 If you are on **MacOS**, make sure you have _Xcode and its developer tools_ installed: `xcode-select --install`
## Installing Rustlings
The following command will download and compile Rustlings:
```bash
cargo install rustlings
```
{% details(summary="If the installation fails…") %}
- Make sure you have the latest Rust version by running `rustup update`
- Try adding the `--locked` flag: `cargo install rustlings --locked`
- Otherwise, please [report the issue](https://github.com/rust-lang/rustlings/issues/new)
{% end %}
## Initialization
After installing Rustlings, run the following command to initialize the `rustlings/` directory:
```bash
rustlings init
```
{% details(summary="If the command <code>rustlings</code> can't be found…") %}
You are probably using Linux and installed Rust using your package manager.
Cargo installs binaries to the directory `~/.cargo/bin`.
Sadly, package managers often don't add `~/.cargo/bin` to your `PATH` environment variable.
- Either add `~/.cargo/bin` manually to `PATH`
- Or uninstall Rust from the package manager and [install it using the official way with `rustup`](https://www.rust-lang.org/tools/install)
{% end %}
Now, go into the newly initialized directory and launch Rustlings for further instructions on getting started with the exercises:
```bash
cd rustlings/
rustlings
```
## Working environment
### Editor
Our general recommendation is [VS Code](https://code.visualstudio.com/) with the [rust-analyzer plugin](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer).
But any editor that supports [rust-analyzer](https://rust-analyzer.github.io/) should be enough for working on the exercises.
### Terminal
While working with Rustlings, please use a modern terminal for the best user experience.
The default terminal on Linux and Mac should be sufficient.
On Windows, we recommend the [Windows Terminal](https://aka.ms/terminal).
## Usage
After being done with the setup, visit the [**usage**](@/usage/index.md) page for some info about using Rustlings 🚀

View File

@ -0,0 +1,55 @@
+++
title = "Usage"
+++
<!-- toc -->
## Doing exercises
The exercises are sorted by topic and can be found in the subdirectory `exercises/<topic>`.
For every topic, there is an additional `README.md` file with some resources to get you started on the topic.
We highly recommend that you have a look at them before you start 📚️
Most exercises contain an error that keeps them from compiling, and it's up to you to fix it!
Some exercises contain tests that need to pass for the exercise to be done ✅
Search for `TODO` and `todo!()` to find out what you need to change.
Ask for hints by entering `h` in the _watch mode_ 💡
## Watch Mode
After the [initialization](@/setup/index.md#initialization), Rustlings can be launched by simply running the command `rustlings`.
This will start the _watch mode_ which walks you through the exercises in a predefined order (what we think is best for newcomers).
It will rerun the current exercise automatically every time you change the exercise's file in the `exercises/` directory.
{% details(summary="If detecting file changes in the <code>exercises/</code> directory fails…") %}
You can add the **`--manual-run`** flag (`rustlings --manual-run`) to manually rerun the current exercise by entering `r` in the watch mode.
Please [report the issue](https://github.com/rust-lang/rustlings/issues/new) with some information about your operating system and whether you run Rustlings in a container or a virtual machine (e.g. WSL).
{% end %}
## Exercise List
In the [watch mode](#watch-mode) (after launching `rustlings`), you can enter `l` to open the interactive exercise list.
The list allows you to…
- See the status of all exercises (done or pending)
- `c`: Continue at another exercise (temporarily skip some exercises or go back to a previous one)
- `r`: Reset status and file of the selected exercise (you need to _reload/reopen_ its file in your editor afterwards)
See the footer of the list for all possible keys.
## Questions?
If you need any help while doing the exercises and the builtin hints aren't helpful, feel free to ask in the [_Q&A_ discussions](https://github.com/rust-lang/rustlings/discussions/categories/q-a?discussions_q=) if your question isn't answered there 💡
## Continuing On
Once you've completed Rustlings, put your new knowledge to good use!
Continue practicing your Rust skills by building your own projects, contributing to Rustlings, or finding other open-source projects to contribute to.
> If you want to create your own Rustlings exercises, visit the [**community exercises**](@/community-exercises/index.md) page 🏗️

54
website/input.css Normal file
View File

@ -0,0 +1,54 @@
@import 'tailwindcss';
@layer base {
h1 {
@apply text-4xl mt-3 mb-3 font-bold;
}
h2 {
@apply text-3xl mt-4 mb-1.5 font-bold;
}
h3 {
@apply text-2xl mt-5 mb-1.5 font-bold;
}
h4 {
@apply text-xl mt-6 mb-1.5 font-bold;
}
p {
@apply mb-2;
}
a {
@apply text-[#FFC832] underline hover:decoration-orange-400 transition duration-300;
}
ul {
@apply mt-2 mb-3 ml-1 list-disc list-inside marker:text-sky-600;
}
ol {
@apply mt-2 mb-3 ml-1 list-decimal list-inside marker:text-sky-500;
}
li {
@apply my-0.5;
}
code {
@apply bg-white/10 px-1 pb-px pt-1 rounded-md;
}
pre code {
@apply bg-inherit p-0 text-inherit;
}
hr {
@apply my-5 rounded-full;
}
img {
@apply md:w-3/4 lg:w-3/5;
}
blockquote {
@apply px-3 pt-2 pb-0.5 mb-4 mt-2 border-s-4 border-white/80 bg-white/7 rounded-sm;
}
pre {
@apply px-2 pt-2 pb-px overflow-x-auto text-sm sm:text-base rounded-sm mt-2 mb-4 after:content-[attr(data-lang)] after:text-[8px] after:opacity-40 selection:bg-white/15;
}
pre code mark {
@apply pb-0.5 pt-1 pr-px text-inherit rounded-xs;
}
}

5
website/justfile Normal file
View File

@ -0,0 +1,5 @@
zola:
zola serve --open
tailwind:
npx @tailwindcss/cli -w -i input.css -o static/main.css

5
website/package.json Normal file
View File

@ -0,0 +1,5 @@
{
"dependencies": {
"@tailwindcss/cli": "^4.1"
}
}

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1200 800" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
<g id="Layer-1" serif:id="Layer 1">
<g transform="matrix(1,0,0,1,1009.4,506.362)">
<path d="M0,-7.203L-12.072,-32.209C-12.009,-33.156 -11.961,-34.107 -11.961,-35.062C-11.961,-63.408 -41.439,-89.533 -91.03,-110.451L-91.03,-93.058C-95.866,-94.977 -100.901,-96.845 -106.147,-98.651L-106.147,-106.759C-177.021,-132.319 -282.53,-148.537 -400.388,-148.537C-503.361,-148.537 -596.917,-136.157 -666.179,-115.983L-666.179,-87.737L-666.181,-87.737L-666.181,-121.925C-737.141,-99.375 -781.135,-68.048 -781.135,-33.41C-781.135,-27.95 -780.034,-22.572 -777.918,-17.297L-785.146,-4.43C-785.146,-4.43 -790.938,3.082 -780.74,18.932C-771.746,32.909 -726.692,87.617 -702.913,116.267C-692.699,130.954 -685.772,140.001 -685.167,139.126C-684.212,137.74 -691.518,110.165 -711.802,78.703C-721.268,61.808 -732.57,39.42 -739.356,22.884C-720.414,34.874 -609.126,90.913 -382.124,90.685C-150.13,90.453 -47.009,17.834 -35.691,7.948C-39.646,23.837 -53.159,55.981 -63.936,78.586C-81.642,110.917 -88.056,139.064 -87.232,140.456C-86.708,141.334 -80.667,132.015 -71.756,116.913C-51.025,87.37 -11.739,30.974 -3.889,16.608C5.007,0.323 0,-7.203 0,-7.203" style="fill:rgb(165,43,0);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,1079.49,294.885)">
<path d="M0,204.135L-79.343,145.689C-80.088,143.089 -80.833,140.488 -81.603,137.908L-55.541,100.154C-52.881,96.314 -52.345,91.322 -54.072,86.943C-55.803,82.585 -59.587,79.461 -64.062,78.696L-108.128,71.217C-109.837,67.732 -111.626,64.301 -113.422,60.898L-94.907,18.51C-93.004,14.193 -93.402,9.175 -95.929,5.256C-98.446,1.319 -102.715,-0.981 -107.267,-0.802L-151.991,0.823C-154.306,-2.193 -156.658,-5.18 -159.058,-8.114L-148.78,-53.546C-147.738,-58.158 -149.054,-62.989 -152.267,-66.34C-155.462,-69.679 -160.105,-71.062 -164.52,-69.979L-208.082,-59.27C-210.902,-61.763 -213.77,-64.223 -216.67,-66.635L-215.103,-113.276C-214.935,-117.997 -217.136,-122.484 -220.915,-125.105C-224.692,-127.741 -229.485,-128.137 -233.616,-126.179L-274.254,-106.858C-277.527,-108.736 -280.819,-110.595 -284.146,-112.395L-291.327,-158.356C-292.056,-163.012 -295.051,-166.968 -299.246,-168.774C-303.431,-170.591 -308.222,-170.002 -311.894,-167.238L-348.126,-140.053C-351.695,-141.238 -355.279,-142.373 -358.905,-143.46L-374.522,-187.045C-376.11,-191.488 -379.772,-194.751 -384.238,-195.669C-388.688,-196.578 -393.266,-195.037 -396.352,-191.589L-426.851,-157.47C-430.536,-157.893 -434.228,-158.28 -437.927,-158.601L-461.476,-198.277C-463.86,-202.295 -468.073,-204.741 -472.615,-204.741C-477.144,-204.741 -481.365,-202.295 -483.733,-198.277L-507.288,-158.601C-510.989,-158.28 -514.696,-157.893 -518.376,-157.47L-548.875,-191.589C-551.965,-195.037 -556.559,-196.578 -560.997,-195.669C-565.457,-194.739 -569.125,-191.488 -570.704,-187.045L-586.333,-143.46C-589.954,-142.373 -593.538,-141.23 -597.113,-140.053L-633.333,-167.238C-637.016,-170.012 -641.811,-170.599 -646.001,-168.774C-650.182,-166.968 -653.189,-163.012 -653.914,-158.356L-661.1,-112.395C-664.422,-110.595 -667.714,-108.746 -670.995,-106.858L-711.629,-126.179C-715.756,-128.145 -720.574,-127.741 -724.333,-125.105C-728.106,-122.484 -730.313,-117.997 -730.143,-113.276L-728.581,-66.635C-731.475,-64.223 -734.337,-61.763 -737.172,-59.27L-780.726,-69.979C-785.149,-71.053 -789.788,-69.679 -792.991,-66.34C-796.212,-62.989 -797.517,-58.158 -796.482,-53.546L-786.225,-8.114C-788.603,-5.169 -790.958,-2.193 -793.267,0.823L-837.991,-0.802C-842.504,-0.937 -846.812,1.319 -849.334,5.256C-851.861,9.175 -852.244,14.193 -850.363,18.51L-831.835,60.898C-833.634,64.301 -835.421,67.732 -837.144,71.217L-881.207,78.696C-885.686,79.45 -889.459,82.572 -891.201,86.943C-892.929,91.322 -892.368,96.314 -889.727,100.154L-863.661,137.908C-863.862,138.575 -864.048,139.247 -864.248,139.916L-937.944,218.201C-937.944,218.201 -949.24,227.052 -932.797,247.855C-918.297,266.206 -843.846,338.951 -804.526,377.06C-787.92,396.408 -776.542,408.389 -775.354,407.353C-773.478,405.708 -783.326,370.506 -816.036,329.204C-841.252,292.148 -873.977,235.155 -866.303,228.586C-866.303,228.586 -857.574,217.505 -840.061,209.529C-839.42,210.041 -840.723,209.022 -840.061,209.529C-840.061,209.529 -470.466,380.02 -127.632,212.413C-88.468,205.388 -64.759,226.368 -64.759,226.368C-56.583,231.108 -77.755,289.712 -95.166,328.505C-118.845,372.555 -122.317,406.927 -120.31,408.119C-119.042,408.876 -110.427,395.766 -98.138,374.902C-67.814,332.649 -10.492,252.1 0,232.534C11.895,210.352 0,204.135 0,204.135" style="fill:rgb(247,76,0);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,917.896,244.679)">
<path d="M0,232.466C0,232.466 53.179,230 123.032,159.004L132.93,137.025C132.93,137.025 24.513,29.177 193.048,-45.266C193.048,-45.266 178.293,-21.154 182.622,72.006C182.622,72.006 233.437,54.357 248.336,-27.934C248.336,-27.934 322.456,69.79 167.834,161.443C167.834,161.443 95.294,277.732 -6.971,266.593L0,232.466Z" style="fill:rgb(247,76,0);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,676.997,488.361)">
<path d="M0,-78.192C0,-78.192 36.935,-118.635 73.871,-78.192C73.871,-78.192 102.893,-24.265 73.871,2.695C73.871,2.695 26.384,40.443 0,2.695C0,2.695 -31.658,-26.964 0,-78.192" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,719.761,425.169)">
<path d="M0,0.004C0,15.75 -9.282,28.518 -20.732,28.518C-32.18,28.518 -41.462,15.75 -41.462,0.004C-41.462,-15.746 -32.18,-28.514 -20.732,-28.514C-9.282,-28.514 0,-15.746 0,0.004" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,512.148,482.736)">
<path d="M0,-83.609C0,-83.609 63.355,-111.661 80.648,-49.047C80.648,-49.047 98.762,23.933 28.618,28.052C28.618,28.052 -60.826,10.824 0,-83.609" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,543.968,426.204)">
<path d="M0,0.002C0,16.241 -9.572,29.411 -21.381,29.411C-33.185,29.411 -42.76,16.241 -42.76,0.002C-42.76,-16.242 -33.185,-29.409 -21.381,-29.409C-9.572,-29.409 0,-16.242 0,0.002" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,593.317,576.574)">
<path d="M0,-40.271L80.796,-46.755C80.796,-46.755 78.058,-33.749 67.517,-23.986C67.517,-23.986 39.727,6.484 7.844,-26.519C7.844,-26.519 2.627,-32.148 0,-40.271" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,269.796,270.778)">
<path d="M0,190.741C-0.667,190.741 -1.321,190.79 -1.973,190.842C-28.207,184.871 -101.946,165.657 -121.437,134.479C-121.437,134.479 -22.21,21.607 -177.297,-50.54L-159.24,74.338C-159.24,74.338 -207.049,42.389 -217.366,-27.008C-217.366,-27.008 -333.789,57.486 -165.982,138.466C-165.982,138.466 -150.762,195.653 -4.633,241.281L-4.526,240.846C-3.055,241.118 -1.549,241.281 0,241.281C13.808,241.281 25.003,229.969 25.003,216.01C25.003,202.054 13.808,190.741 0,190.741" style="fill:rgb(247,76,0);fill-rule:nonzero;"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="1434.979px" height="947px" viewBox="0 0 1434.979 947" enable-background="new 0 0 1434.979 947" xml:space="preserve">
<!--<rect fill="#FFFFFF" width="1434.979" height="947"/>-->
<path fill="#8F1F1D" d="M712.827,368.579c-131.633,0-251.228,15.825-339.77,41.615v220.298
c88.542,25.79,208.137,41.614,339.77,41.614c150.657,0,285.535-20.729,376.134-53.402V421.986
C998.361,389.311,863.483,368.579,712.827,368.579"/>
<path fill="#8F1F1D" d="M1094.847,567.972c-3.856-10.663-4.629-24.154-1.36-37.162c5.85-23.289,22.421-36.198,37.013-28.833
c3.618,1.827,6.773,4.73,9.387,8.418c0.239-0.001,0.479,0,0.715,0.016c0,0,44.552,53.106,3.313,116.003
c-0.896,3.569-76.534,91.718-94.043,94.524C1038.411,722.773,1064.762,630.939,1094.847,567.972"/>
<path fill="#8F1F1D" d="M363.903,557.551c5.3-9.631,7.158-22.788,4.217-36.426c-5.266-24.416-23.91-41.109-41.642-37.285
c-4.398,0.948-8.325,3.072-11.666,6.099c-0.282-0.059-0.564-0.113-0.845-0.153c0,0-56.292,41.952-12.057,113.924
c0.805,3.741,83.851,108.838,104.311,115.764C419.612,724.004,394.974,626.947,363.903,557.551"/>
<path fill="#E23A26" d="M284.891,563.596l0.007,0.015C285.083,563.755,285.255,563.887,284.891,563.596"/>
<path fill="#E33B26" d="M1180.543,488.433c-0.88-3.064-1.756-6.126-2.662-9.162l30.683-44.451c3.13-4.522,3.771-10.398,1.73-15.555
c-2.04-5.13-6.49-8.81-11.76-9.71l-51.887-8.805c-2.008-4.102-4.115-8.142-6.229-12.15l21.797-49.903
c2.243-5.087,1.769-10.995-1.203-15.608c-2.961-4.636-7.99-7.344-13.349-7.133l-52.656,1.913c-2.727-3.55-5.496-7.068-8.322-10.521
l12.102-53.49c1.225-5.433-0.322-11.118-4.104-15.064c-3.762-3.932-9.229-5.559-14.426-4.283l-51.289,12.608
c-3.321-2.935-6.699-5.833-10.114-8.673l1.849-54.914c0.197-5.559-2.394-10.842-6.845-13.925
c-4.445-3.104-10.093-3.573-14.955-1.266l-47.848,22.747c-3.854-2.21-7.728-4.4-11.644-6.517l-8.455-54.115
c-0.857-5.483-4.386-10.139-9.326-12.266c-4.923-2.137-10.568-1.447-14.891,1.808l-42.659,32.007
c-4.2-1.395-8.419-2.732-12.692-4.011l-18.386-51.316c-1.87-5.229-6.182-9.071-11.438-10.151c-5.238-1.072-10.63,0.742-14.263,4.802
l-35.907,40.171c-4.342-0.5-8.685-0.956-13.043-1.331l-27.723-46.713c-2.811-4.732-7.771-7.612-13.116-7.612
c-5.334,0-10.304,2.88-13.09,7.612l-27.733,46.713c-4.358,0.375-8.722,0.831-13.056,1.331l-35.91-40.171
c-3.636-4.06-9.047-5.874-14.268-4.802c-5.255,1.092-9.573,4.922-11.433,10.151l-18.402,51.316
c-4.26,1.279-8.481,2.627-12.691,4.011l-42.644-32.007c-4.336-3.266-9.98-3.955-14.916-1.808c-4.919,2.127-8.461,6.783-9.313,12.266
l-8.461,54.115c-3.914,2.117-7.789,4.294-11.653,6.517L436.1,168.34c-4.858-2.316-10.529-1.838-14.954,1.266
c-4.445,3.083-7.042,8.366-6.84,13.925l1.835,54.914c-3.405,2.84-6.774,5.738-10.112,8.673L354.75,234.51
c-5.211-1.265-10.67,0.351-14.441,4.283c-3.795,3.946-5.332,9.631-4.113,15.064l12.079,53.49c-2.802,3.467-5.575,6.971-8.293,10.521
l-52.655-1.913c-5.314-0.157-10.386,2.497-13.356,7.133c-2.974,4.613-3.425,10.521-1.211,15.608l21.814,49.903
c-2.119,4.008-4.224,8.048-6.249,12.15l-51.882,8.805c-5.271,0.888-9.715,4.566-11.765,9.71c-2.037,5.157-1.375,11.033,1.735,15.555
l30.69,44.451c-0.236,0.784-0.455,1.576-0.69,2.364l-16.863,17.911l45.341,64.05c0,0,435.152,200.731,838.797,3.396
C1169.796,558.719,1180.543,488.433,1180.543,488.433"/>
<path d="M795.716,446.557c0,0,48.162-52.734,96.324,0c0,0,37.844,70.318,0,105.473c0,0-61.922,49.223-96.324,0
C795.716,552.029,754.434,513.354,795.716,446.557"/>
<path fill="#FFFFFF" d="M855.154,481.097c0,19.782-11.66,35.82-26.041,35.82c-14.379,0-26.04-16.038-26.04-35.82
c0-19.782,11.661-35.821,26.04-35.821C843.494,445.275,855.154,461.315,855.154,481.097"/>
<path d="M578.401,430.129c0,0,84.436-37.385,107.481,46.059c0,0,24.141,97.261-69.339,102.751
C616.543,578.939,497.34,555.98,578.401,430.129"/>
<rect x="187.424" y="75.529" fill="none" width="1060" height="782"/>
<path fill="#FFFFFF" d="M627.514,481.096c0,20.579-12.13,37.27-27.095,37.27c-14.959,0-27.092-16.69-27.092-37.27
c0-20.583,12.133-37.27,27.092-37.27C615.384,443.826,627.514,460.513,627.514,481.096"/>
<path fill="#E33B26" d="M299.026,574.745c10.967-12.463,37.611-27.557,35.57-46.282c-3.653-33.526-31.456-57.999-62.099-54.658
c-7.599,0.827-14.658,3.292-20.923,7.035c-0.463-0.106-0.925-0.211-1.388-0.294c0,0-103.632,50.873-44.564,152.657
c0.557,5.137,117.847,155.668,150.787,167.131C377.968,807.836,336.498,671.694,299.026,574.745"/>
<path fill="#E33B26" d="M1140.973,570.202c-12.692-10.7-46.162-20.418-46.92-39.238c-1.355-33.697,22.512-62.021,53.312-63.26
c7.638-0.308,14.983,1.083,21.734,3.857c0.442-0.174,0.884-0.347,1.329-0.497c0,0,110.025,34.951,66.695,144.366
c0.21,5.163-93.468,171.416-124.345,187.635C1092.57,813.681,1118.285,671.635,1140.973,570.202"/>
<rect x="187.484" y="75.843" fill="none" width="1059.75" height="781.686"/>
<rect x="187.424" y="75.529" fill="none" width="1060" height="782"/>
<g>
<path fill="#E33B26" d="M283.144,565.511c0,0-137.214-4.942-161.62-140.761l57.596-25.427c0,0-13.912,96.957,106.615,110.022
L283.144,565.511"/>
<path fill="#E33B26" d="M127.552,333.083c0,0-24.965-49.774-65.807-113.261C18.721,241.035-2.671,299.05,13.482,357.484
c17.846,64.558,74.749,105.16,127.097,90.69s80.318-78.535,62.471-143.092c-7.909-28.618-23.501-52.519-42.963-69.011
C150.611,287.113,127.552,333.083,127.552,333.083"/>
</g>
<rect x="187.484" y="75.843" fill="none" width="1059.75" height="781.686"/>
<g>
<path fill="#E33B26" d="M1148.012,565.511c0,0,137.214-4.942,161.62-140.761l-57.596-25.428c0,0,13.912,96.957-106.615,110.022
L1148.012,565.511"/>
<path fill="#E33B26" d="M1303.604,333.083c0,0,24.966-49.774,65.808-113.261c43.023,21.212,64.416,79.228,48.262,137.662
c-17.846,64.558-74.748,105.16-127.096,90.689c-52.348-14.47-80.318-78.534-62.472-143.091
c7.909-28.618,23.501-52.519,42.964-69.011C1280.544,287.113,1303.604,333.083,1303.604,333.083"/>
</g>
<path d="M807.895,626.942c-7.131-58.735-72.193-61.431-72.193-61.431c-50.936,11.227-59.183,47.369-57.392,75.104L807.895,626.942z"
/>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -0,0 +1,61 @@
<svg version="1.1" height="106" width="106" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="logo" transform="translate(53, 53)">
<path id="r" transform="translate(0.5, 0.5)" fill="white" stroke="white" stroke-width="1" stroke-linejoin="round" d="
M -9,-15 H 4 C 12,-15 12,-7 4,-7 H -9 Z
M -40,22 H 0 V 11 H -9 V 3 H 1 C 12,3 6,22 15,22 H 40
V 3 H 34 V 5 C 34,13 25,12 24,7 C 23,2 19,-2 18,-2 C 33,-10 24,-26 12,-26 H -35
V -15 H -25 V 11 H -40 Z" />
<g id="gear" mask="url(#holes)">
<circle r="43" fill="none" stroke="white" stroke-width="9" />
<g id="cogs">
<polygon id="cog" stroke="white" stroke-width="3" stroke-linejoin="round" points="46,3 51,0 46,-3" />
<use xlink:href="#cog" transform="rotate(11.25)" />
<use xlink:href="#cog" transform="rotate(22.50)" />
<use xlink:href="#cog" transform="rotate(33.75)" />
<use xlink:href="#cog" transform="rotate(45.00)" />
<use xlink:href="#cog" transform="rotate(56.25)" />
<use xlink:href="#cog" transform="rotate(67.50)" />
<use xlink:href="#cog" transform="rotate(78.75)" />
<use xlink:href="#cog" transform="rotate(90.00)" />
<use xlink:href="#cog" transform="rotate(101.25)" />
<use xlink:href="#cog" transform="rotate(112.50)" />
<use xlink:href="#cog" transform="rotate(123.75)" />
<use xlink:href="#cog" transform="rotate(135.00)" />
<use xlink:href="#cog" transform="rotate(146.25)" />
<use xlink:href="#cog" transform="rotate(157.50)" />
<use xlink:href="#cog" transform="rotate(168.75)" />
<use xlink:href="#cog" transform="rotate(180.00)" />
<use xlink:href="#cog" transform="rotate(191.25)" />
<use xlink:href="#cog" transform="rotate(202.50)" />
<use xlink:href="#cog" transform="rotate(213.75)" />
<use xlink:href="#cog" transform="rotate(225.00)" />
<use xlink:href="#cog" transform="rotate(236.25)" />
<use xlink:href="#cog" transform="rotate(247.50)" />
<use xlink:href="#cog" transform="rotate(258.75)" />
<use xlink:href="#cog" transform="rotate(270.00)" />
<use xlink:href="#cog" transform="rotate(281.25)" />
<use xlink:href="#cog" transform="rotate(292.50)" />
<use xlink:href="#cog" transform="rotate(303.75)" />
<use xlink:href="#cog" transform="rotate(315.00)" />
<use xlink:href="#cog" transform="rotate(326.25)" />
<use xlink:href="#cog" transform="rotate(337.50)" />
<use xlink:href="#cog" transform="rotate(348.75)" />
</g>
<g id="mounts">
<polygon id="mount" stroke="white" stroke-width="6" stroke-linejoin="round" points="-7,-42 0,-35 7,-42" />
<use xlink:href="#mount" transform="rotate(72)" />
<use xlink:href="#mount" transform="rotate(144)" />
<use xlink:href="#mount" transform="rotate(216)" />
<use xlink:href="#mount" transform="rotate(288)" />
</g>
</g>
<mask id="holes">
<rect x="-60" y="-60" width="120" height="120" fill="white"/>
<circle id="hole" cy="-40" r="3" />
<use xlink:href="#hole" transform="rotate(72)" />
<use xlink:href="#hole" transform="rotate(144)" />
<use xlink:href="#hole" transform="rotate(216)" />
<use xlink:href="#hole" transform="rotate(288)" />
</mask>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,14 @@
{% extends "base.html" %}
{% block content %}
<div class="flex flex-col mx-auto text-center">
<h1>DON'T PANIC!</h1>
<h2>404: Page not found!</h2>
<img class="mx-auto max-h-[50vh]"
src="{{ get_url(path='images/panic.svg') | safe }}"
alt="">
<a class="text-2xl font-bold" href="{{ get_url(path='@/_index.md') }}">Back to homepage</a>
</div>
{% endblock %}

View File

@ -0,0 +1,2 @@
<a class="text-white no-underline transition-none hover:underline"
href="#{{ id }}"></a>

View File

@ -0,0 +1,92 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{%- set timestamp = now(timestamp=true) -%}
{%- if page.title -%}
{% set_global title = page.title %}
{%- elif section.title -%}
{% set_global title = section.title %}
{%- else -%}
{% set_global title = config.title %}
{%- endif -%}
{%- if page.description -%}
{% set_global description = page.description %}
{%- elif section.description -%}
{% set_global description = section.description %}
{%- else -%}
{% set_global description = config.description %}
{%- endif -%}
{%- if page.permalink -%}
{% set_global permalink = page.permalink %}
{%- elif section.permalink -%}
{% set_global permalink = section.permalink %}
{%- endif %}
<title>{%- block title -%}{{- title -}}{%- endblock -%}</title>
<meta name="description"
content="{%- block description -%}{{- description -}}{%- endblock -%}">
<link rel="icon"
type="image/x-icon"
href="{{ get_url(path=config.extra.logo_path) | safe }}?v={{ timestamp }}">
<link href="{{ get_url(path='main.css') | safe }}?v={{ timestamp }}"
rel="stylesheet">
<meta property="og:title" content="{{ title }}">
<meta property="og:description" content="{{ description }}">
<meta property="og:image"
content="{{ get_url(path=config.extra.logo_path) | safe }}?v={{ timestamp }}">
{% if permalink %}<meta property="og:url" content="{{ permalink | safe }}">{% endif %}
</head>
<body class="flex flex-col p-2 mx-auto min-h-screen text-lg text-white break-words lg:px-5 2xl:container bg-[#2A3439]">
<header class="flex flex-col gap-x-4 items-center py-2 px-4 mb-1 rounded-sm sm:flex-row sm:rounded-full bg-black/30">
<a class="transition duration-500 hover:scale-110"
href="{{ get_url(path='@/_index.md') | safe }}"
aria-hidden="true">
<img class="w-12 h-12"
src="{{ get_url(path=config.extra.logo_path) | safe }}"
alt="">
</a>
<nav class="flex flex-col gap-x-6 items-center font-bold sm:flex-row">
{% for menu_item in config.extra.menu_items %}
{%- if menu_item.url is starting_with("@") -%}
{% set_global menu_item_url = get_url(path=menu_item.url) %}
{%- else -%}
{% set_global menu_item_url = menu_item.url %}
{%- endif %}
<a class="p-1 no-underline" href="{{ menu_item_url | safe }}">{{ menu_item.name }}</a>
{% endfor %}
</nav>
</header>
<main class="leading-relaxed">
{% block content %}{% endblock %}
</main>
<footer class="pt-2 pb-1 mt-auto text-sm text-center">
<div class="inline-flex gap-x-1.5 items-center mx-auto mt-2">
<img class="w-8 h-8"
src="{{ get_url(path='images/rust_logo.svg') | safe }}"
alt="">
<div class="italic">Rustlings is an official Rust project</div>
</div>
<nav class="flex flex-col gap-y-3 justify-around py-3 mt-3 rounded-sm sm:flex-row sm:rounded-full bg-black/30">
{% for footer_item in config.extra.footer_items %}
<a class="no-underline" href="{{ footer_item.url | safe }}">{{ footer_item.name }}</a>
{% endfor %}
</nav>
</footer>
</body>
</html>

View File

@ -0,0 +1,9 @@
{% extends "base.html" %}
{% block content %}
<div class="m-3">
<h1>Rustlings</h1>
{{ section.content | safe }}
</div>
{% endblock %}

View File

@ -0,0 +1,39 @@
{% extends "base.html" %}
{% block content %}
<article>
<h1>{{ page.title }}</h1>
<div class="py-0.5 px-4 my-3 rounded-xl border-double border-s-4">
<nav>
<ul class="ml-0 list-none">
{% for parent in page.toc %}
{% if parent.level == 2 %}
<li>
{#- -#}
<a href="{{ parent.permalink | safe }}">{{ parent.title }}</a>
{#- -#}
{% if parent.children %}
<ul class="my-0 ml-5 list-none">
{% for child in parent.children %}
{% if child.level == 3 %}
<li>
{#- -#}
<a class="text-base" href="{{ child.permalink | safe }}">{{ child.title }}</a>
{#- -#}
</li>
{% endif %}
{% endfor %}
</ul>
{% endif %}
{#- -#}
</li>
{% endif %}
{% endfor %}
</ul>
</nav>
</div>
{{ page.content | safe }}
</article>
{% endblock %}

View File

@ -0,0 +1,9 @@
<details>
<summary>
<strong>{{ summary | safe }}</strong> (<em>click to expand</em>)
</summary>
<blockquote class="pt-1 mx-0.5 mt-1 rounded-none border-dashed border-x-3 border-b-3">
{{ body | markdown | safe }}
</blockquote>
</details>