forked from Rust-related/rustlings
Compare commits
219 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4bab596677 | ||
|
|
9172a5bf27 | ||
|
|
b18a8c3036 | ||
|
|
60b369a2fd | ||
|
|
db3f332507 | ||
|
|
ef218cd5d0 | ||
|
|
361a7f501e | ||
|
|
360344ab6c | ||
|
|
db5ad7f42f | ||
|
|
9b50da484f | ||
|
|
f3036315a0 | ||
|
|
97a723508c | ||
|
|
03ddf3683b | ||
|
|
06ca7b8718 | ||
|
|
ef99b5cb9e | ||
|
|
5b1edf5f4f | ||
|
|
4338c58079 | ||
|
|
e38c82ccbb | ||
|
|
a40a4dd43b | ||
|
|
60edde0f59 | ||
|
|
848e3f9294 | ||
|
|
fd237df59a | ||
|
|
d9e0b103c4 | ||
|
|
0907e65245 | ||
|
|
d6caefb139 | ||
|
|
d9b63641e5 | ||
|
|
3b0e7393f7 | ||
|
|
d3d414c7b1 | ||
|
|
6c392609a3 | ||
|
|
1d53bd1b54 | ||
|
|
124708acd9 | ||
|
|
f9f8a37bc7 | ||
|
|
4f1a440962 | ||
|
|
4c4dd20be3 | ||
|
|
e0334f79fe | ||
|
|
b06c843179 | ||
|
|
b86a532e28 | ||
|
|
c658a997f3 | ||
|
|
870776d03b | ||
|
|
013a88a1e6 | ||
|
|
03c5baf35c | ||
|
|
5464fcd7e6 | ||
|
|
b7308825ec | ||
|
|
346753b673 | ||
|
|
3bbc3001c9 | ||
|
|
b59f444bbc | ||
|
|
a307599b0b | ||
|
|
432d1f84ea | ||
|
|
b5fbf59c0c | ||
|
|
695f927893 | ||
|
|
f403d9e1b6 | ||
|
|
95499f18dd | ||
|
|
bc0b4e9f9a | ||
|
|
b0dc014040 | ||
|
|
b48663030b | ||
|
|
dace3e3953 | ||
|
|
c9ccedcff6 | ||
|
|
4d97c31c0f | ||
|
|
7c1d8ebf49 | ||
|
|
95b6160b54 | ||
|
|
c466d01da9 | ||
|
|
7ed2316040 | ||
|
|
7d53dc4c95 | ||
|
|
7150a9eb79 | ||
|
|
37cbcd9049 | ||
|
|
3e46d8c50a | ||
|
|
08eb634db5 | ||
|
|
573d5a2acd | ||
|
|
d3df105167 | ||
|
|
2c9c31e8a2 | ||
|
|
a28b9eda84 | ||
|
|
802dcfc987 | ||
|
|
ceb98475e2 | ||
|
|
337f6b1521 | ||
|
|
0ffeb14402 | ||
|
|
611d62951f | ||
|
|
415bf695be | ||
|
|
d87a3b6ca5 | ||
|
|
064f057b10 | ||
|
|
17ff88902b | ||
|
|
75c06bb7f4 | ||
|
|
7e5793b642 | ||
|
|
c163bfe563 | ||
|
|
e91647b023 | ||
|
|
aaf8cad778 | ||
|
|
8738518699 | ||
|
|
9011d34987 | ||
|
|
2512701e2f | ||
|
|
0cbcb8964c | ||
|
|
13564207cb | ||
|
|
fb87a26f4f | ||
|
|
4817abcc14 | ||
|
|
3a00274335 | ||
|
|
de695c46f3 | ||
|
|
87ac600b7c | ||
|
|
1b47fd97c0 | ||
|
|
45f789114b | ||
|
|
7850a73d95 | ||
|
|
1ebb4d25a6 | ||
|
|
0bed579a4b | ||
|
|
c6c6d27232 | ||
|
|
b5d440fdc3 | ||
|
|
4700e8a12c | ||
|
|
8753dd6b2e | ||
|
|
f80fbca12e | ||
|
|
d8f4b06c91 | ||
|
|
1955313362 | ||
|
|
95a597eb82 | ||
|
|
c2455bc676 | ||
|
|
2af9e89ba5 | ||
|
|
6ec2e194ae | ||
|
|
295ad2e4bd | ||
|
|
628ef55337 | ||
|
|
b6b94e3e96 | ||
|
|
6765a0b61a | ||
|
|
436c95f4cc | ||
|
|
c6888685e6 | ||
|
|
208a593216 | ||
|
|
2d1d531550 | ||
|
|
a712e484d0 | ||
|
|
4f9f0907c3 | ||
|
|
3a2fe2c394 | ||
|
|
f24861957a | ||
|
|
1a633e2757 | ||
|
|
9fecdba101 | ||
|
|
7af38e684d | ||
|
|
e8da6869f8 | ||
|
|
57b3727b3e | ||
|
|
278edc0b96 | ||
|
|
cb60c8887c | ||
|
|
46814d397a | ||
|
|
734fc482eb | ||
|
|
520dfdc464 | ||
|
|
2267f99684 | ||
|
|
bf74a3d0a7 | ||
|
|
adf3ddd968 | ||
|
|
f80c2edc3d | ||
|
|
04520ae7ad | ||
|
|
e36dd7a120 | ||
|
|
edc8528dde | ||
|
|
47e490a997 | ||
|
|
596e7f36cc | ||
|
|
512ded81c4 | ||
|
|
69a9e9cafc | ||
|
|
54a74fd638 | ||
|
|
a51d6f1309 | ||
|
|
f6a657a0c3 | ||
|
|
8c24763259 | ||
|
|
dc468882cc | ||
|
|
5fc787f4e4 | ||
|
|
8fa598ae7e | ||
|
|
2f700991f3 | ||
|
|
b4a6b87e24 | ||
|
|
984e9fea7c | ||
|
|
8339007633 | ||
|
|
23b9aa3a15 | ||
|
|
69fe9626da | ||
|
|
f387f4c1d9 | ||
|
|
40fe3aa741 | ||
|
|
b30973afa1 | ||
|
|
3d8bef4bc3 | ||
|
|
2673177b17 | ||
|
|
6d5369d4d0 | ||
|
|
b9d1e636a4 | ||
|
|
7e26418952 | ||
|
|
61c17cb349 | ||
|
|
fda18e8895 | ||
|
|
7ec6986965 | ||
|
|
74ab9924b4 | ||
|
|
a28000acc4 | ||
|
|
08548abcc2 | ||
|
|
5927a781a3 | ||
|
|
e73fff3bd4 | ||
|
|
8dff0df266 | ||
|
|
5ee7dfb5c2 | ||
|
|
9a3586878d | ||
|
|
a99433c62d | ||
|
|
e76ca5e2b9 | ||
|
|
48bab77609 | ||
|
|
a063bcfb4c | ||
|
|
c5f49cfa48 | ||
|
|
9bcd4198c5 | ||
|
|
29dc8ea9fa | ||
|
|
fa91814aa9 | ||
|
|
0b91db2195 | ||
|
|
7b2d42b0f0 | ||
|
|
bd3bdd620b | ||
|
|
8b4562e102 | ||
|
|
63d8986f2a | ||
|
|
ecaecc2f76 | ||
|
|
78194b4441 | ||
|
|
44699e9b1b | ||
|
|
9978c17d5f | ||
|
|
3cc7e0377c | ||
|
|
d2abc359cc | ||
|
|
7c0d269279 | ||
|
|
8db85946af | ||
|
|
7019f4d178 | ||
|
|
fcd77a83cc | ||
|
|
ae444eb3da | ||
|
|
425c9821e0 | ||
|
|
46c6fb2c82 | ||
|
|
374c3874af | ||
|
|
1eb6c1e469 | ||
|
|
06af3ffc99 | ||
|
|
65dc019fa6 | ||
|
|
a56ccb6f4f | ||
|
|
d9872f2615 | ||
|
|
298be671b9 | ||
|
|
fbfd4f25e7 | ||
|
|
d12735a573 | ||
|
|
1aec7c1152 | ||
|
|
0b55809bb9 | ||
|
|
bde6f7470c | ||
|
|
53ec59ed95 | ||
|
|
ed1ee38923 | ||
|
|
26cf4989a2 | ||
|
|
6e60f441e9 | ||
|
|
d07de879a7 |
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[alias]
|
||||
dev = ["run", "--", "dev"]
|
||||
@@ -1,7 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*.rs]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
41
.github/workflows/rust.yml
vendored
41
.github/workflows/rust.yml
vendored
@@ -1,10 +1,18 @@
|
||||
name: Rustlings Tests
|
||||
name: Check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- website
|
||||
- .github/workflows/website.yml
|
||||
- '*.md'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- website
|
||||
- .github/workflows/website.yml
|
||||
- '*.md'
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -13,31 +21,36 @@ jobs:
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: cargo clippy -- --deny warnings
|
||||
- uses: actions/checkout@v6
|
||||
- name: Clippy
|
||||
run: cargo clippy -- --deny warnings
|
||||
fmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: DavidAnson/markdownlint-cli2-action@v16
|
||||
with:
|
||||
globs: "exercises/**/*.md"
|
||||
- name: Run cargo fmt
|
||||
- uses: actions/checkout@v6
|
||||
- name: rustfmt
|
||||
run: cargo fmt --all --check
|
||||
test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: swatinem/rust-cache@v2
|
||||
- name: Run cargo test
|
||||
- name: cargo test
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
run: cargo test --workspace
|
||||
dev-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: swatinem/rust-cache@v2
|
||||
- name: Run rustlings dev check
|
||||
run: cargo run -- dev check --require-solutions
|
||||
- name: rustlings dev check
|
||||
run: cargo dev check --require-solutions
|
||||
rumdl:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rvben/rumdl@v0
|
||||
|
||||
87
.github/workflows/web.yml
vendored
87
.github/workflows/web.yml
vendored
@@ -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
|
||||
50
.github/workflows/website.yml
vendored
Normal file
50
.github/workflows/website.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
name: Website
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- website
|
||||
- .github/workflows/website.yml
|
||||
|
||||
jobs:
|
||||
rumdl:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rvben/rumdl@v0
|
||||
build:
|
||||
needs: rumdl
|
||||
defaults:
|
||||
run:
|
||||
working-directory: website
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- 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.22.1/zola-v0.22.1-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@v5
|
||||
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@v5
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -6,10 +6,6 @@ Cargo.lock
|
||||
# State file
|
||||
.rustlings-state.txt
|
||||
|
||||
# oranda
|
||||
public/
|
||||
.netlify
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
.direnv/
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# MD013/line-length Line length, Expected: 80
|
||||
MD013: false
|
||||
7
.rumdl.toml
Normal file
7
.rumdl.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[global]
|
||||
output-format = "full"
|
||||
disable = ["MD013", "MD057"]
|
||||
|
||||
[per-file-ignores]
|
||||
"website/content/_index.md" = ["MD041"]
|
||||
"website/content/**/*.md" = ["MD028", "MD033"]
|
||||
333
CHANGELOG.md
333
CHANGELOG.md
@@ -1,4 +1,47 @@
|
||||
<a name="6.4.0"></a>
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- Automatically open the current file if Rustlings is running in a VS Code terminal
|
||||
- Automatically open the current file with `$EDITOR` in a new pane if Rustlings is running in [Zellij](https://zellij.dev)
|
||||
- New argument `--no-editor` to disable automatic opening of the current file in VS Code or Zellij
|
||||
- New argument `--edit-cmd` to communicate with an editor running in a different process to open the current exercise
|
||||
- Show the file link of the current exercise when running `rustlings hint` and `rustlings reset`
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix integer overflow on big terminal widths [@gabfec](https://github.com/gabfec)
|
||||
- Fix workspace detection on Windows [@senekor](https://github.com/senekor)
|
||||
|
||||
### Changed
|
||||
|
||||
- Avoid initializing a nested Git repository [@senekor](https://github.com/senekor)
|
||||
- `vecs2`: Removed the use of `map` and `collect`, which are only taught later.
|
||||
- `structs3`: Rewrote the exercise to make users type method syntax themselves.
|
||||
- Rename the exercises for smart pointers and conversions so they're sorted alphabetically. [@foxfromworld](https://github.com/foxfromworld)
|
||||
- `vecs1`: Remove array literal. Some learners assumed their task is to convert it to a vector.
|
||||
- `conversions2`: Redesign the context such that infallible conversion makes sense.
|
||||
|
||||
## 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)
|
||||
|
||||
@@ -11,7 +54,7 @@
|
||||
- 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))
|
||||
- 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
|
||||
|
||||
@@ -29,8 +72,6 @@
|
||||
|
||||
- Fix bad contrast in the list on terminals with a light theme.
|
||||
|
||||
<a name="6.3.0"></a>
|
||||
|
||||
## 6.3.0 (2024-08-29)
|
||||
|
||||
### Added
|
||||
@@ -70,8 +111,6 @@
|
||||
- Fix the list when the terminal height is too low.
|
||||
- Restore the terminal after an error in the list.
|
||||
|
||||
<a name="6.2.0"></a>
|
||||
|
||||
## 6.2.0 (2024-08-09)
|
||||
|
||||
### Added
|
||||
@@ -88,34 +127,28 @@
|
||||
- Run the final check of all exercises in parallel.
|
||||
- Small exercise improvements.
|
||||
|
||||
<a name="6.1.0"></a>
|
||||
|
||||
## 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).
|
||||
|
||||
#### Changed
|
||||
### Changed
|
||||
|
||||
- Make enum variants more consistent between enum exercises.
|
||||
- `iterators3`: Teach about the possible case of integer overflow during division.
|
||||
|
||||
#### Fixed
|
||||
### Fixed
|
||||
|
||||
- Exit with a helpful error message on missing/unsupported terminal/TTY.
|
||||
- Mark the last exercise as done.
|
||||
|
||||
<a name="6.0.1"></a>
|
||||
|
||||
## 6.0.1 (2024-07-04)
|
||||
|
||||
Small exercise improvements and fixes.
|
||||
Most importantly, fixed that the exercise `clippy1` was already solved 😅
|
||||
|
||||
<a name="6.0.0"></a>
|
||||
|
||||
## 6.0.0 (2024-07-03)
|
||||
|
||||
This release is the result of a complete rewrite to deliver a ton of new features and improvements ✨
|
||||
@@ -173,23 +206,21 @@ 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 📎
|
||||
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?
|
||||
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)!
|
||||
|
||||
<a name="5.6.1"></a>
|
||||
Then follow the link to the guide about [community exercises](https://rustlings.rust-lang.org/community-exercises)!
|
||||
|
||||
## 5.6.1 (2023-09-18)
|
||||
|
||||
#### Changed
|
||||
### Changed
|
||||
|
||||
- Converted all exercises with assertions to test mode.
|
||||
|
||||
#### Fixed
|
||||
### Fixed
|
||||
|
||||
- `cow1`: Reverted regression introduced by calling `to_mut` where it
|
||||
shouldn't have been called, and clarified comment.
|
||||
@@ -198,11 +229,9 @@ 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.
|
||||
- `enums3`: Fixed formatting with `rustfmt`.
|
||||
|
||||
<a name="5.6.0"></a>
|
||||
|
||||
## 5.6.0 (2023-09-04)
|
||||
|
||||
#### Added
|
||||
### Added
|
||||
|
||||
- New exercise: `if3`, teaching the user about `if let` statements.
|
||||
- `hashmaps2`: Added an extra test function to check if the amount of fruits is higher than zero.
|
||||
@@ -210,7 +239,7 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- `if1`: Added a test case to check equal values.
|
||||
- `if3`: Added a note specifying that there are no test changes needed.
|
||||
|
||||
#### Changed
|
||||
### Changed
|
||||
|
||||
- Swapped the order of threads and smart pointer exercises.
|
||||
- Rewrote the CLI to use `clap` - it's matured much since we switched to `argh` :)
|
||||
@@ -218,7 +247,7 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- `move_semantics`: Switched 1-4 to tests, and rewrote them to be way simpler, while still teaching about the same
|
||||
concepts.
|
||||
|
||||
#### Fixed
|
||||
### Fixed
|
||||
|
||||
- `iterators5`:
|
||||
- Removed an outdated part of the hint.
|
||||
@@ -233,25 +262,21 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- `cow1`: Added `.to_mut()` to distinguish from the previous test case.
|
||||
- `threads2`: Updated hint text to reference the correct book heading.
|
||||
|
||||
#### Housekeeping
|
||||
### Housekeeping
|
||||
|
||||
- Cleaned up the explanation paragraphs at the start of each exercise.
|
||||
- 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.
|
||||
|
||||
<a name="5.5.1"></a>
|
||||
|
||||
## 5.5.1 (2023-05-17)
|
||||
|
||||
#### Fixed
|
||||
### Fixed
|
||||
|
||||
- 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)
|
||||
|
||||
#### Added
|
||||
### Added
|
||||
|
||||
- `strings2`: Added a reference to the book chapter for reference conversion
|
||||
- `lifetimes`: Added a link to the lifetimekata project
|
||||
@@ -259,7 +284,7 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- Added a `!` prefix command to watch mode that runs an external command
|
||||
- Added a `--success-hints` option to watch mode that shows hints on exercise success
|
||||
|
||||
#### Changed
|
||||
### Changed
|
||||
|
||||
- `vecs2`: Renamed iterator variable bindings for clarify
|
||||
- `lifetimes`: Changed order of book references
|
||||
@@ -268,7 +293,7 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- `options2`: Improved tests for layering options
|
||||
- `modules2`: Added more information to the hint
|
||||
|
||||
#### Fixed
|
||||
### Fixed
|
||||
|
||||
- `errors2`: Corrected a comment wording
|
||||
- `iterators2`: Fixed a spelling mistake in the hint text
|
||||
@@ -278,33 +303,29 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- `options3`: Changed exercise to panic when no match is found
|
||||
- `rustlings lsp` now generates absolute paths, which should fix VSCode `rust-analyzer` usage on Windows
|
||||
|
||||
#### Housekeeping
|
||||
### Housekeeping
|
||||
|
||||
- Added a markdown linter to run on GitHub actions
|
||||
- Split quick installation section into two code blocks
|
||||
|
||||
<a name="5.4.1"></a>
|
||||
|
||||
## 5.4.1 (2023-03-10)
|
||||
|
||||
#### Changed
|
||||
### Changed
|
||||
|
||||
- `vecs`: Added links to `iter_mut` and `map` to README.md
|
||||
- `cow1`: Changed main to tests
|
||||
- `iterators1`: Formatted according to rustfmt
|
||||
|
||||
#### Fixed
|
||||
### Fixed
|
||||
|
||||
- `errors5`: Unified undisclosed type notation
|
||||
- `arc1`: Improved readability by avoiding implicit dereference
|
||||
- `macros4`: Prevented auto-fix by adding `#[rustfmt::skip]`
|
||||
- `cli`: Actually show correct progress percentages
|
||||
|
||||
<a name="5.4.0"></a>
|
||||
|
||||
## 5.4.0 (2023-02-12)
|
||||
|
||||
#### Changed
|
||||
### Changed
|
||||
|
||||
- Reordered exercises
|
||||
- Unwrapped `standard_library_types` into `iterators` and `smart_pointers`
|
||||
@@ -316,7 +337,7 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- Made progress bar update proportional to amount of files verified
|
||||
- Decreased `watch` delay from 2 to 1 second
|
||||
|
||||
#### Fixed
|
||||
### Fixed
|
||||
|
||||
- Capitalized "Rust" in exercise hints
|
||||
- **enums3**: Removed superfluous tuple brackets
|
||||
@@ -326,25 +347,23 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- Fixed a typo in a method name
|
||||
- Specified the edition in `rustc` commands
|
||||
|
||||
#### Housekeeping
|
||||
### Housekeeping
|
||||
|
||||
- Bumped min Rust version to 1.58 in installation script
|
||||
|
||||
<a name="5.3.0"></a>
|
||||
|
||||
## 5.3.0 (2022-12-23)
|
||||
|
||||
#### Added
|
||||
### Added
|
||||
|
||||
- **cli**: Added a percentage display in watch mode
|
||||
- Added a `flake.nix` for Nix users
|
||||
|
||||
#### Changed
|
||||
### Changed
|
||||
|
||||
- **structs3**: Added an additional test
|
||||
- **macros**: Added a link to MacroKata in the README
|
||||
|
||||
#### Fixed
|
||||
### Fixed
|
||||
|
||||
- **strings3**: Added a link to `std` in the hint
|
||||
- **threads1**: Corrected a hint link
|
||||
@@ -358,63 +377,55 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- **enums2**: Removed unnecessary indirection of self
|
||||
- **enums3**: Added an extra tuple comment
|
||||
|
||||
#### Housekeeping
|
||||
### Housekeeping
|
||||
|
||||
- Added a VSCode extension recommendation
|
||||
- Applied some Clippy and rustfmt formatting
|
||||
- Added a note on Windows PowerShell and other shell compatibility
|
||||
|
||||
<a name="5.2.1"></a>
|
||||
|
||||
## 5.2.1 (2022-09-06)
|
||||
|
||||
#### Fixed
|
||||
### Fixed
|
||||
|
||||
- **quiz1**: Reworded the comment to actually reflect what's going on in the tests.
|
||||
Also added another assert just to make sure.
|
||||
- **rc1**: Fixed a typo in the hint.
|
||||
- **lifetimes**: Add quotes to the `println!` output, for readability.
|
||||
|
||||
#### Housekeeping
|
||||
### Housekeeping
|
||||
|
||||
- Fixed a typo in README.md
|
||||
|
||||
<a name="5.2.0"></a>
|
||||
|
||||
## 5.2.0 (2022-08-27)
|
||||
|
||||
#### Added
|
||||
### Added
|
||||
|
||||
- Added a `reset` command
|
||||
|
||||
#### Changed
|
||||
### Changed
|
||||
|
||||
- **options2**: Convert the exercise to use tests
|
||||
|
||||
#### Fixed
|
||||
### Fixed
|
||||
|
||||
- **threads3**: Fixed a typo
|
||||
- **quiz1**: Adjusted the explanations to be consistent with
|
||||
the tests
|
||||
|
||||
<a name="5.1.1"></a>
|
||||
|
||||
## 5.1.1 (2022-08-17)
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed an incorrect assertion in options1
|
||||
|
||||
<a name="5.1.0"></a>
|
||||
|
||||
## 5.1.0 (2022-08-16)
|
||||
|
||||
#### Features
|
||||
### Features
|
||||
|
||||
- Added a new `rc1` exercise.
|
||||
- Added a new `cow1` exercise.
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- **variables5**: Corrected reference to previous exercise
|
||||
- **functions4**: Fixed line number reference
|
||||
@@ -434,18 +445,16 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- Added more granular tests
|
||||
- Fixed some comment syntax shenanigans in info.toml
|
||||
|
||||
#### Housekeeping
|
||||
### Housekeeping
|
||||
|
||||
- Fixed a typo in .editorconfig
|
||||
- Fixed a typo in integration_tests.rs
|
||||
- Clarified manual installation instructions using `cargo install --path .`
|
||||
- Added a link to our Zulip in the readme file
|
||||
|
||||
<a name="5.0.0"></a>
|
||||
|
||||
## 5.0.0 (2022-07-16)
|
||||
|
||||
#### Features
|
||||
### Features
|
||||
|
||||
- Hint comments in exercises now also include a reference to the
|
||||
`hint` watch mode subcommand.
|
||||
@@ -477,7 +486,7 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- Added 3 new lifetimes exercises.
|
||||
- Added 3 new traits exercises.
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- **variables2**: Made output messages more verbose.
|
||||
- **variables5**: Added a nudging hint about shadowing.
|
||||
@@ -501,7 +510,7 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
`Box<dyn Error>`.
|
||||
- **try_from_into**: Fixed the function name in comment.
|
||||
|
||||
#### Removed
|
||||
### Removed
|
||||
|
||||
- Removed the legacy LSP feature that was using `mod.rs` files.
|
||||
- Removed `quiz4`.
|
||||
@@ -509,67 +518,61 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
order, and I've always felt like they didn't quite fit in with the mostly
|
||||
simple, book-following style we've had in Rustlings.
|
||||
|
||||
#### Housekeeping
|
||||
### Housekeeping
|
||||
|
||||
- Added missing exercises to the book index.
|
||||
- Updated spacing in Cargo.toml.
|
||||
- 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)
|
||||
|
||||
#### Features
|
||||
### Features
|
||||
|
||||
- Added a progress indicator for `rustlings watch`.
|
||||
- The installation script now checks for Rustup being installed.
|
||||
- Added a `rustlings lsp` command to enable `rust-analyzer`.
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- **move_semantics5**: Replaced "in vogue" with "in scope" in hint.
|
||||
- **if2**: Fixed a typo in the hint.
|
||||
- **variables1**: Fixed an incorrect line reference in the hint.
|
||||
- Fixed an out of bounds check in the installation Bash script.
|
||||
|
||||
#### Housekeeping
|
||||
### Housekeeping
|
||||
|
||||
- Replaced the git.io URL with the fully qualified URL because of git.io's sunsetting.
|
||||
- Removed the deprecated Rust GitPod extension.
|
||||
|
||||
<a name="4.7.1"></a>
|
||||
|
||||
## 4.7.1 (2022-04-20)
|
||||
|
||||
#### Features
|
||||
### Features
|
||||
|
||||
- The amount of dependency crates that need to be compiled went down from ~65 to
|
||||
~45 by bumping dependency versions.
|
||||
- The minimum Rust version in the install scripts has been bumped to 1.56.0 (this isn't in
|
||||
the release itself, since install scripts don't really get versioned)
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- **arc1**: A small part has been rewritten using a more functional code style (#968).
|
||||
- **using_as**: A small part has been refactored to use `sum` instead of `fold`, resulting
|
||||
in better readability.
|
||||
|
||||
#### Housekeeping
|
||||
### Housekeeping
|
||||
|
||||
- The changelog will now be manually written instead of being automatically generated by the
|
||||
Git log.
|
||||
|
||||
<a name="4.7.0"></a>
|
||||
|
||||
## 4.7.0 (2022-04-14)
|
||||
|
||||
#### Features
|
||||
### Features
|
||||
|
||||
- Add move_semantics6.rs exercise (#908) ([3f0e1303](https://github.com/rust-lang/rustlings/commit/3f0e1303e0b3bf3fecc0baced3c8b8a37f83c184))
|
||||
- **intro:** Add intro section. ([21c9f441](https://github.com/rust-lang/rustlings/commit/21c9f44168394e08338fd470b5f49b1fd235986f))
|
||||
- Include exercises folder in the project structure behind a feature, enabling rust-analyzer to work (#917) ([179a75a6](https://github.com/rust-lang/rustlings/commit/179a75a68d03ac9518dec2297fb17f91a4fc506b))
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- Fix a few spelling mistakes ([1c0fe3cb](https://github.com/rust-lang/rustlings/commit/1c0fe3cbcca85f90b3985985b8e265ee872a2ab2))
|
||||
- **cli:**
|
||||
@@ -596,16 +599,14 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- **structs3.rs:** assigned value to cents_per_gram in test ([d1ee2daf](https://github.com/rust-lang/rustlings/commit/d1ee2daf14f19105e6db3f9c610f44293d688532))
|
||||
- **traits1:** rename test functions to snake case (#854) ([1663a16e](https://github.com/rust-lang/rustlings/commit/1663a16eade6ca646b6ed061735f7982434d530d))
|
||||
|
||||
#### Documentation improvements
|
||||
### Documentation improvements
|
||||
|
||||
- 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))
|
||||
|
||||
<a name="4.6.0"></a>
|
||||
|
||||
## 4.6.0 (2021-09-25)
|
||||
|
||||
#### Features
|
||||
### Features
|
||||
|
||||
- add advanced_errs2 ([abd6b70c](https://github.com/rust-lang/rustlings/commit/abd6b70c72dc6426752ff41f09160b839e5c449e))
|
||||
- add advanced_errs1 ([882d535b](https://github.com/rust-lang/rustlings/commit/882d535ba8628d5e0b37e8664b3e2f26260b2671))
|
||||
@@ -614,7 +615,7 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- **modules:** update exercises, add modules3 (#822) ([dfd2fab4](https://github.com/rust-lang/rustlings/commit/dfd2fab4f33d1bf59e2e5ee03123c0c9a67a9481))
|
||||
- **quiz1:** add default function name in comment (#838) ([0a11bad7](https://github.com/rust-lang/rustlings/commit/0a11bad71402b5403143d642f439f57931278c07))
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- Correct small typo in exercises/conversions/from_str.rs ([86cc8529](https://github.com/rust-lang/rustlings/commit/86cc85295ae36948963ae52882e285d7e3e29323))
|
||||
- **cli:** typo in exercise.rs (#848) ([06d5c097](https://github.com/rust-lang/rustlings/commit/06d5c0973a3dffa3c6c6f70acb775d4c6630323c))
|
||||
@@ -625,16 +626,14 @@ 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))
|
||||
- **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)
|
||||
|
||||
#### Features
|
||||
### Features
|
||||
|
||||
- Add move_semantics5 exercise. (#746) ([399ab328](https://github.com/rust-lang/rustlings/commit/399ab328d8d407265c09563aa4ef4534b2503ff2))
|
||||
- **cli:** Add "next" to run the next unsolved exercise. (#785) ([d20e413a](https://github.com/rust-lang/rustlings/commit/d20e413a68772cd493561f2651cf244e822b7ca5))
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- rename result1 to errors4 ([50ab289d](https://github.com/rust-lang/rustlings/commit/50ab289da6b9eb19a7486c341b00048c516b88c0))
|
||||
- move_semantics5 hints ([1b858285](https://github.com/rust-lang/rustlings/commit/1b85828548f46f58b622b5e0c00f8c989f928807))
|
||||
@@ -647,11 +646,9 @@ 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))
|
||||
- **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)
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- Fix spelling error in main.rs ([91ee27f2](https://github.com/rust-lang/rustlings/commit/91ee27f22bd3797a9db57e5fd430801c170c5db8))
|
||||
- typo in default out text ([644c49f1](https://github.com/rust-lang/rustlings/commit/644c49f1e04cbb24e95872b3a52b07d692ae3bc8))
|
||||
@@ -679,7 +676,7 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- **threads1:** line number correction ([7857b0a6](https://github.com/rust-lang/rustlings/commit/7857b0a689b0847f48d8c14cbd1865e3b812d5ca))
|
||||
- **try_from_into:** use trait objects ([2e93a588](https://github.com/rust-lang/rustlings/commit/2e93a588e0abe8badb7eafafb9e7d073c2be5df8))
|
||||
|
||||
#### Features
|
||||
### Features
|
||||
|
||||
- Replace clap with argh ([7928122f](https://github.com/rust-lang/rustlings/commit/7928122fcef9ca7834d988b1ec8ca0687478beeb))
|
||||
- Replace emojis when NO_EMOJI env variable present ([8d62a996](https://github.com/rust-lang/rustlings/commit/8d62a9963708dbecd9312e8bcc4b47049c72d155))
|
||||
@@ -690,11 +687,9 @@ 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))
|
||||
- added progress info ([c0e3daac](https://github.com/rust-lang/rustlings/commit/c0e3daacaf6850811df5bc57fa43e0f249d5cfa4))
|
||||
|
||||
<a name="4.3.0"></a>
|
||||
|
||||
## 4.3.0 (2020-12-29)
|
||||
|
||||
#### Features
|
||||
### Features
|
||||
|
||||
- Rewrite default out text ([44d39112](https://github.com/rust-lang/rustlings/commit/44d39112ff122b29c9793fe52e605df1612c6490))
|
||||
- match exercise order to book chapters (#541) ([033bf119](https://github.com/rust-lang/rustlings/commit/033bf1198fc8bfce1b570e49da7cde010aa552e3))
|
||||
@@ -702,7 +697,7 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- add "rustlings list" command ([838f9f30](https://github.com/rust-lang/rustlings/commit/838f9f30083d0b23fd67503dcf0fbeca498e6647))
|
||||
- **try_from_into:** remove duplicate annotation ([04f1d079](https://github.com/rust-lang/rustlings/commit/04f1d079aa42a2f49af694bc92c67d731d31a53f))
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- update structs README ([bcf14cf6](https://github.com/rust-lang/rustlings/commit/bcf14cf677adb3a38a3ac3ca53f3c69f61153025))
|
||||
- added missing exercises to info.toml ([90cfb6ff](https://github.com/rust-lang/rustlings/commit/90cfb6ff28377531bfc34acb70547bdb13374f6b))
|
||||
@@ -714,18 +709,16 @@ 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))
|
||||
- **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)
|
||||
|
||||
#### Features
|
||||
### Features
|
||||
|
||||
- Add HashMap exercises ([633c00cf](https://github.com/rust-lang/rustlings/commit/633c00cf8071e1e82959a3010452a32f34f29fc9))
|
||||
- Add Vec exercises ([0c12fa31](https://github.com/rust-lang/rustlings/commit/0c12fa31c57c03c6287458a0a8aca7afd057baf6))
|
||||
- **primitive_types6:** Add a test (#548) ([2b1fb2b7](https://github.com/rust-lang/rustlings/commit/2b1fb2b739bf9ad8d6b7b12af25fee173011bfc4))
|
||||
- **try_from_into:** Add tests (#571) ([95ccd926](https://github.com/rust-lang/rustlings/commit/95ccd92616ae79ba287cce221101e0bbe4f68cdc))
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- log error output when inotify limit is exceeded ([d61b4e5a](https://github.com/rust-lang/rustlings/commit/d61b4e5a13b44d72d004082f523fa1b6b24c1aca))
|
||||
- more unique temp_file ([5643ef05](https://github.com/rust-lang/rustlings/commit/5643ef05bc81e4a840e9456f4406a769abbe1392))
|
||||
@@ -736,11 +729,9 @@ 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))
|
||||
- **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)
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- Update rustlings version in Cargo.lock ([1cc40bc9](https://github.com/rust-lang/rustlings/commit/1cc40bc9ce95c23d56f6d91fa1c4deb646231fef))
|
||||
- **arc1:** index mod should equal thread count ([b4062ef6](https://github.com/rust-lang/rustlings/commit/b4062ef6993e80dac107c4093ea85166ad3ee0fa))
|
||||
@@ -750,7 +741,7 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- **structs3:** Small adjustment of variable name ([114b54cb](https://github.com/rust-lang/rustlings/commit/114b54cbdb977234b39e5f180d937c14c78bb8b2))
|
||||
- **using_as:** Add test so that proper type is returned. (#512) ([3286c5ec](https://github.com/rust-lang/rustlings/commit/3286c5ec19ea5fb7ded81d047da5f8594108a490))
|
||||
|
||||
#### Features
|
||||
### Features
|
||||
|
||||
- Added iterators1.rs exercise ([9642f5a3](https://github.com/rust-lang/rustlings/commit/9642f5a3f686270a4f8f6ba969919ddbbc4f7fdd))
|
||||
- Add ability to run rustlings on repl.it (#471) ([8f7b5bd0](https://github.com/rust-lang/rustlings/commit/8f7b5bd00eb83542b959830ef55192d2d76db90a))
|
||||
@@ -760,16 +751,14 @@ 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))
|
||||
- **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)
|
||||
|
||||
#### Breaking Changes
|
||||
### Breaking Changes
|
||||
|
||||
- Add a --nocapture option to display test harnesses' outputs ([8ad5f9bf](https://github.com/rust-lang/rustlings/commit/8ad5f9bf531a4848b1104b7b389a20171624c82f))
|
||||
- Rename test to quiz, fixes #244 ([010a0456](https://github.com/rust-lang/rustlings/commit/010a04569282149cea7f7a76fc4d7f4c9f0f08dd))
|
||||
|
||||
#### Features
|
||||
### Features
|
||||
|
||||
- Add traits README ([173bb141](https://github.com/rust-lang/rustlings/commit/173bb14140c5530cbdb59e53ace3991a99d804af))
|
||||
- Add box1.rs exercise ([7479a473](https://github.com/rust-lang/rustlings/commit/7479a4737bdcac347322ad0883ca528c8675e720))
|
||||
@@ -778,7 +767,7 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- Added exercise structs3.rs ([b66e2e09](https://github.com/rust-lang/rustlings/commit/b66e2e09622243e086a0f1258dd27e1a2d61c891))
|
||||
- Add exercise variables6 covering const (#352) ([5999acd2](https://github.com/rust-lang/rustlings/commit/5999acd24a4f203292be36e0fd18d385887ec481))
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- Change then to than ([ddd98ad7](https://github.com/rust-lang/rustlings/commit/ddd98ad75d3668fbb10eff74374148aa5ed2344d))
|
||||
- rename quiz1 to tests1 in info (#420) ([0dd1c6ca](https://github.com/rust-lang/rustlings/commit/0dd1c6ca6b389789e0972aa955fe17aa15c95f29))
|
||||
@@ -803,15 +792,13 @@ 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))
|
||||
- **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)
|
||||
|
||||
#### Breaking Changes
|
||||
### Breaking Changes
|
||||
|
||||
- make "compile" exercises print output (#278) ([3b6d5c](https://github.com/fmoko/rustlings/commit/3b6d5c3aaa27a242a832799eb66e96897d26fde3))
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- **primitive_types:** revert primitive_types4 (#296) ([b3a3351e](https://github.com/rust-lang/rustlings/commit/b3a3351e8e6a0bdee07077d7b0382953821649ae))
|
||||
- **run:** compile clippy exercise files (#295) ([3ab084a4](https://github.com/rust-lang/rustlings/commit/3ab084a421c0f140ae83bf1fc3f47b39342e7373))
|
||||
@@ -820,30 +807,26 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- remove duplicate not done comment (#292) ([dab90f](https://github.com/fmoko/rustlings/commit/dab90f7b91a6000fe874e3d664f244048e5fa342))
|
||||
- don't hardcode documentation version for traits (#288) ([30e6af](https://github.com/fmoko/rustlings/commit/30e6af60690c326fb5d3a9b7335f35c69c09137d))
|
||||
|
||||
#### Features
|
||||
### Features
|
||||
|
||||
- add Option2 exercise (#290) ([86b5c08b](https://github.com/rust-lang/rustlings/commit/86b5c08b9bea1576127a7c5f599f5752072c087d))
|
||||
- add exercise for option (#282) ([135e5d47](https://github.com/rust-lang/rustlings/commit/135e5d47a7c395aece6f6022117fb20c82f2d3d4))
|
||||
- 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))
|
||||
|
||||
<a name="2.2.1"></a>
|
||||
## 2.2.1 (2020-02-27)
|
||||
|
||||
### 2.2.1 (2020-02-27)
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- Re-add cloning the repo to install scripts ([3d9b03c5](https://github.com/rust-lang/rustlings/commit/3d9b03c52b8dc51b140757f6fd25ad87b5782ef5))
|
||||
|
||||
#### Features
|
||||
### Features
|
||||
|
||||
- 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)
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- Update deps to version compatible with aarch64-pc-windows (#263) ([19a93428](https://github.com/rust-lang/rustlings/commit/19a93428b3c73d994292671f829bdc8e5b7b3401))
|
||||
- **docs:**
|
||||
@@ -858,7 +841,7 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- Change test command ([fe10e06c](https://github.com/rust-lang/rustlings/commit/fe10e06c3733ddb4a21e90d09bf79bfe618e97ce)
|
||||
- Correct test command in tests1.rs comment (#263) ([39fa7ae](https://github.com/rust-lang/rustlings/commit/39fa7ae8b70ad468da49b06f11b2383135a63bcf))
|
||||
|
||||
#### Features
|
||||
### Features
|
||||
|
||||
- Add variables5.rs exercise (#264) ([0c73609e](https://github.com/rust-lang/rustlings/commit/0c73609e6f2311295e95d6f96f8c747cfc4cba03))
|
||||
- Show a completion message when watching (#253) ([d25ee55a](https://github.com/rust-lang/rustlings/commit/d25ee55a3205882d35782e370af855051b39c58c))
|
||||
@@ -868,11 +851,9 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- Added traits exercises (#274 but specifically #216, which originally added
|
||||
this :heart:) ([b559cdd](https://github.com/rust-lang/rustlings/commit/b559cdd73f32c0d0cfc1feda39f82b3e3583df17))
|
||||
|
||||
<a name="2.1.0"></a>
|
||||
|
||||
## 2.1.0 (2019-11-27)
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- add line numbers in several exercises and hints ([b565c4d3](https://github.com/rust-lang/rustlings/commit/b565c4d3e74e8e110bef201a082fa1302722a7c3))
|
||||
- **arc1:** Fix some words in the comment ([c42c3b21](https://github.com/rust-lang/rustlings/commit/c42c3b2101df9164c8cd7bb344def921e5ba3e61))
|
||||
@@ -883,37 +864,33 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- **strings2:** update line number in hint ([a09f684f](https://github.com/rust-lang/rustlings/commit/a09f684f05c58d239a6fc59ec5f81c2533e8b820))
|
||||
- **variables1:** Correct wrong word in comment ([fda5a470](https://github.com/rust-lang/rustlings/commit/fda5a47069e0954f16a04e8e50945e03becb71a5))
|
||||
|
||||
#### Features
|
||||
### Features
|
||||
|
||||
- **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)
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- **default:** Clarify the installation procedure ([c371b853](https://github.com/rust-lang/rustlings/commit/c371b853afa08947ddeebec0edd074b171eeaae0))
|
||||
- **info:** Fix trailing newlines for hints ([795b6e34](https://github.com/rust-lang/rustlings/commit/795b6e348094a898e9227a14f6232f7bb94c8d31))
|
||||
- **run:** make `run` never prompt ([4b265465](https://github.com/rust-lang/rustlings/commit/4b26546589f7d2b50455429482cf1f386ceae8b3))
|
||||
|
||||
#### Breaking Changes
|
||||
### Breaking Changes
|
||||
|
||||
- Refactor hint system ([9bdb0a12](https://github.com/rust-lang/rustlings/commit/9bdb0a12e45a8e9f9f6a4bd4a9c172c5376c7f60))
|
||||
- improve `watch` execution mode ([2cdd6129](https://github.com/rust-lang/rustlings/commit/2cdd61294f0d9a53775ee24ad76435bec8a21e60))
|
||||
- Index exercises by name ([627cdc07](https://github.com/rust-lang/rustlings/commit/627cdc07d07dfe6a740e885e0ddf6900e7ec336b))
|
||||
- **run:** makes `run` never prompt ([4b265465](https://github.com/rust-lang/rustlings/commit/4b26546589f7d2b50455429482cf1f386ceae8b3))
|
||||
|
||||
#### Features
|
||||
### Features
|
||||
|
||||
- **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))
|
||||
|
||||
<a name="1.5.1"></a>
|
||||
## 1.5.1 (2019-11-11)
|
||||
|
||||
### 1.5.1 (2019-11-11)
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- **errors3:** Update hint ([dcfb427b](https://github.com/rust-lang/rustlings/commit/dcfb427b09585f0193f0a294443fdf99f11c64cb), closes [#185](https://github.com/rust-lang/rustlings/issues/185))
|
||||
- **if1:** Remove `return` reference ([ad03d180](https://github.com/rust-lang/rustlings/commit/ad03d180c9311c0093e56a3531eec1a9a70cdb45))
|
||||
@@ -922,11 +899,9 @@ 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))
|
||||
- **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)
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- **test1:** Rewrite logic ([79a56942](https://github.com/rust-lang/rustlings/commit/79a569422c8309cfc9e4aed25bf4ab3b3859996b))
|
||||
- **installation:** Fix rustlings installation check ([7a252c47](https://github.com/rust-lang/rustlings/commit/7a252c475551486efb52f949b8af55803b700bc6))
|
||||
@@ -942,27 +917,23 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- Swap assertion parameter order ([4086d463](https://github.com/rust-lang/rustlings/commit/4086d463a981e81d97781851d17db2ced290f446))
|
||||
- renamed function name to snake case closes #180 ([89d5186c](https://github.com/rust-lang/rustlings/commit/89d5186c0dae8135ecabf90ee8bb35949bc2d29b))
|
||||
|
||||
#### Features
|
||||
### Features
|
||||
|
||||
- Add enums exercises ([dc150321](https://github.com/rust-lang/rustlings/commit/dc15032112fc485226a573a18139e5ce928b1755))
|
||||
- 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))
|
||||
|
||||
<a name="1.4.1"></a>
|
||||
## 1.4.1 (2019-08-13)
|
||||
|
||||
### 1.4.1 (2019-08-13)
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- **iterators2:** Remove syntax resulting in misleading error message ([4cde8664](https://github.com/rust-lang/rustlings/commit/4cde86643e12db162a66e62f23b78962986046ac))
|
||||
- **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))
|
||||
|
||||
<a name="1.4.0"></a>
|
||||
|
||||
## 1.4.0 (2019-07-13)
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- **installation:** Fix rustlings installation check ([7a252c47](https://github.com/rust-lang/rustlings/commit/7a252c475551486efb52f949b8af55803b700bc6))
|
||||
- **iterators:** Rename iterator3.rs ([433d2115](https://github.com/rust-lang/rustlings/commit/433d2115bc1c04b6d34a335a18c9a8f3e2672bc6))
|
||||
@@ -971,20 +942,18 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- **cli:** Check if changed exercise file exists before calling verify ([ba85ca3](https://github.com/rust-lang/rustlings/commit/ba85ca32c4cfc61de46851ab89f9c58a28f33c88))
|
||||
- **structs1:** Fix the irrefutable let pattern warning ([cc6a141](https://github.com/rust-lang/rustlings/commit/cc6a14104d7c034eadc98297eaaa972d09c50b1f))
|
||||
|
||||
#### Features
|
||||
### Features
|
||||
|
||||
- **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))
|
||||
|
||||
<a name="1.3.0"></a>
|
||||
## 1.3.0 (2019-06-05)
|
||||
|
||||
### 1.3.0 (2019-06-05)
|
||||
|
||||
#### Features
|
||||
### Features
|
||||
|
||||
- Adds a simple exercise for structures (#163, @briankung)
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- Add Result type signature as it is difficult for new comers to understand Generics and Error all at once. (#157, @veggiemonk)
|
||||
- Rustfmt and whitespace fixes (#161, @eddyp)
|
||||
@@ -993,37 +962,29 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- Fix broken link (#164, @HanKruiger)
|
||||
- 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
|
||||
|
||||
<a name="1.2.1"></a>
|
||||
## 1.2.1 (2019-04-22)
|
||||
|
||||
### 1.2.1 (2019-04-22)
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- Fix the `--nocapture` feature (@komaeda)
|
||||
- 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
|
||||
|
||||
- Add errors to exercises that compile without user changes (@yvan-sraka)
|
||||
- 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
|
||||
|
||||
- Fix permissions on exercise files (@zacanger, #133)
|
||||
- Make installation checks more thorough (@komaeda, 1b3469f236bc6979c27f6e1a04e4138a88e55de3)
|
||||
@@ -1033,9 +994,7 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
||||
- Fix links by deleting book version (@diodfr, #142)
|
||||
- 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)
|
||||
- Start verification at most recently modified file (#120)
|
||||
@@ -1044,16 +1003,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)
|
||||
- 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`)
|
||||
- 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
|
||||
|
||||
<a name="1.0.0"></a>
|
||||
|
||||
### 1.0.0 (2019-03-06)
|
||||
## 1.0.0 (2019-03-06)
|
||||
|
||||
Initial release.
|
||||
|
||||
@@ -14,7 +14,7 @@ I want to …
|
||||
|
||||
## Issues
|
||||
|
||||
You can open an issue [here](https://github.com/rust-lang/rustlings/issues/new).
|
||||
You can [open an issue](https://github.com/rust-lang/rustlings/issues/new).
|
||||
If you're reporting a bug, please include the output of the following commands:
|
||||
|
||||
- `cargo --version`
|
||||
|
||||
687
Cargo.lock
generated
687
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
33
Cargo.toml
33
Cargo.toml
@@ -1,12 +1,11 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
exclude = [
|
||||
"tests/test_exercises",
|
||||
"dev",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "6.4.0"
|
||||
version = "6.5.0"
|
||||
authors = [
|
||||
"Mo Bitar <mo8it@proton.me>", # https://github.com/mo8it
|
||||
"Liv <mokou@fastmail.com>", # https://github.com/shadows-withal
|
||||
@@ -15,12 +14,12 @@ authors = [
|
||||
]
|
||||
repository = "https://github.com/rust-lang/rustlings"
|
||||
license = "MIT"
|
||||
edition = "2021" # On Update: Update the edition of the `rustfmt` command that checks the solutions.
|
||||
rust-version = "1.80"
|
||||
edition = "2024" # On Update: Update the edition of `rustfmt` in `dev check` and `CARGO_TOML` in `dev new`.
|
||||
rust-version = "1.88"
|
||||
|
||||
[workspace.dependencies]
|
||||
serde = { version = "1.0.214", features = ["derive"] }
|
||||
toml_edit = { version = "0.22.22", default-features = false, features = ["parse", "serde"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
toml = { version = "1", default-features = false, features = ["std", "parse", "serde"] }
|
||||
|
||||
[package]
|
||||
name = "rustlings"
|
||||
@@ -46,21 +45,21 @@ include = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.93"
|
||||
clap = { version = "4.5.20", features = ["derive"] }
|
||||
crossterm = { version = "0.28.1", default-features = false, features = ["windows", "events"] }
|
||||
notify = "7.0.0"
|
||||
os_pipe = "1.2.1"
|
||||
rustlings-macros = { path = "rustlings-macros", version = "=6.4.0" }
|
||||
serde_json = "1.0.132"
|
||||
anyhow = "1"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
crossterm = { version = "0.29", default-features = false, features = ["windows", "events"] }
|
||||
notify = "8"
|
||||
rustlings-macros = { path = "rustlings-macros", version = "=6.5.0" }
|
||||
serde_json = "1"
|
||||
serde.workspace = true
|
||||
toml_edit.workspace = true
|
||||
shlex = "1"
|
||||
toml.workspace = true
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
rustix = { version = "0.38.38", default-features = false, features = ["std", "stdio", "termios"] }
|
||||
rustix = { version = "1.0", default-features = false, features = ["std", "stdio", "termios"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.14.0"
|
||||
tempfile = "3"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
@@ -84,8 +83,6 @@ infinite_loop = "deny"
|
||||
mem_forget = "deny"
|
||||
dbg_macro = "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]
|
||||
workspace = true
|
||||
|
||||
166
README.md
166
README.md
@@ -1,165 +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.
|
||||
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:
|
||||
|
||||
- [日本語版 Rustlings](https://github.com/sotanengel/rustlings-jp):A Japanese 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) 🎉
|
||||
## ➡️ [rustlings.rust-lang.org](https://rustlings.rust-lang.org) ⬅️
|
||||
|
||||
@@ -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 😃
|
||||
5
build.rs
Normal file
5
build.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
fn main() {
|
||||
// Fix building from source on Windows because it can't handle file links.
|
||||
#[cfg(windows)]
|
||||
let _ = std::fs::copy("dev/Cargo.toml", "dev-Cargo.toml");
|
||||
}
|
||||
16
clippy.toml
16
clippy.toml
@@ -1,15 +1,11 @@
|
||||
disallowed-types = [
|
||||
# Inefficient. Use `.queue(…)` instead.
|
||||
"crossterm::style::Stylize",
|
||||
"crossterm::style::styled_content::StyledContent",
|
||||
{ path = "crossterm::style::Stylize", reason = "inefficient, use `.queue(…)` instead" },
|
||||
{ path = "crossterm::style::styled_content::StyledContent", reason = "inefficient, use `.queue(…)` instead" },
|
||||
]
|
||||
|
||||
disallowed-methods = [
|
||||
# Inefficient. Use `.queue(…)` instead.
|
||||
"crossterm::style::style",
|
||||
# Use `thread::Builder::spawn` instead and handle the error.
|
||||
"std::thread::spawn",
|
||||
"std::thread::Scope::spawn",
|
||||
# Return `ExitCode` instead.
|
||||
"std::process::exit",
|
||||
{ path = "crossterm::style::style", reason = "inefficient, use `.queue(…)` instead" },
|
||||
{ path = "std::thread::spawn", replacement = "std::thread::Builder::spawn", reason = "handle the error" },
|
||||
{ path = "std::thread::Scope::spawn", replacement = "std::thread::Builder::spawn", reason = "handle the error" },
|
||||
{ path = "std::process::exit", replacement = "std::process::ExitCode" },
|
||||
]
|
||||
|
||||
@@ -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 = [
|
||||
{ name = "intro1", path = "../exercises/00_intro/intro1.rs" },
|
||||
{ name = "intro1_sol", path = "../solutions/00_intro/intro1.rs" },
|
||||
@@ -150,14 +150,14 @@ bin = [
|
||||
{ name = "iterators4_sol", path = "../solutions/18_iterators/iterators4.rs" },
|
||||
{ name = "iterators5", path = "../exercises/18_iterators/iterators5.rs" },
|
||||
{ name = "iterators5_sol", path = "../solutions/18_iterators/iterators5.rs" },
|
||||
{ name = "box1", path = "../exercises/19_smart_pointers/box1.rs" },
|
||||
{ name = "box1_sol", path = "../solutions/19_smart_pointers/box1.rs" },
|
||||
{ name = "rc1", path = "../exercises/19_smart_pointers/rc1.rs" },
|
||||
{ name = "rc1_sol", path = "../solutions/19_smart_pointers/rc1.rs" },
|
||||
{ name = "arc1", path = "../exercises/19_smart_pointers/arc1.rs" },
|
||||
{ name = "arc1_sol", path = "../solutions/19_smart_pointers/arc1.rs" },
|
||||
{ name = "cow1", path = "../exercises/19_smart_pointers/cow1.rs" },
|
||||
{ name = "cow1_sol", path = "../solutions/19_smart_pointers/cow1.rs" },
|
||||
{ name = "smart_pointers1", path = "../exercises/19_smart_pointers/smart_pointers1.rs" },
|
||||
{ name = "smart_pointers1_sol", path = "../solutions/19_smart_pointers/smart_pointers1.rs" },
|
||||
{ name = "smart_pointers2", path = "../exercises/19_smart_pointers/smart_pointers2.rs" },
|
||||
{ name = "smart_pointers2_sol", path = "../solutions/19_smart_pointers/smart_pointers2.rs" },
|
||||
{ name = "smart_pointers3", path = "../exercises/19_smart_pointers/smart_pointers3.rs" },
|
||||
{ name = "smart_pointers3_sol", path = "../solutions/19_smart_pointers/smart_pointers3.rs" },
|
||||
{ name = "smart_pointers4", path = "../exercises/19_smart_pointers/smart_pointers4.rs" },
|
||||
{ name = "smart_pointers4_sol", path = "../solutions/19_smart_pointers/smart_pointers4.rs" },
|
||||
{ name = "threads1", path = "../exercises/20_threads/threads1.rs" },
|
||||
{ name = "threads1_sol", path = "../solutions/20_threads/threads1.rs" },
|
||||
{ name = "threads2", path = "../exercises/20_threads/threads2.rs" },
|
||||
@@ -178,21 +178,21 @@ bin = [
|
||||
{ name = "clippy2_sol", path = "../solutions/22_clippy/clippy2.rs" },
|
||||
{ name = "clippy3", path = "../exercises/22_clippy/clippy3.rs" },
|
||||
{ name = "clippy3_sol", path = "../solutions/22_clippy/clippy3.rs" },
|
||||
{ name = "using_as", path = "../exercises/23_conversions/using_as.rs" },
|
||||
{ name = "using_as_sol", path = "../solutions/23_conversions/using_as.rs" },
|
||||
{ name = "from_into", path = "../exercises/23_conversions/from_into.rs" },
|
||||
{ name = "from_into_sol", path = "../solutions/23_conversions/from_into.rs" },
|
||||
{ name = "from_str", path = "../exercises/23_conversions/from_str.rs" },
|
||||
{ name = "from_str_sol", path = "../solutions/23_conversions/from_str.rs" },
|
||||
{ name = "try_from_into", path = "../exercises/23_conversions/try_from_into.rs" },
|
||||
{ name = "try_from_into_sol", path = "../solutions/23_conversions/try_from_into.rs" },
|
||||
{ name = "as_ref_mut", path = "../exercises/23_conversions/as_ref_mut.rs" },
|
||||
{ name = "as_ref_mut_sol", path = "../solutions/23_conversions/as_ref_mut.rs" },
|
||||
{ name = "conversions1", path = "../exercises/23_conversions/conversions1.rs" },
|
||||
{ name = "conversions1_sol", path = "../solutions/23_conversions/conversions1.rs" },
|
||||
{ name = "conversions2", path = "../exercises/23_conversions/conversions2.rs" },
|
||||
{ name = "conversions2_sol", path = "../solutions/23_conversions/conversions2.rs" },
|
||||
{ name = "conversions3", path = "../exercises/23_conversions/conversions3.rs" },
|
||||
{ name = "conversions3_sol", path = "../solutions/23_conversions/conversions3.rs" },
|
||||
{ name = "conversions4", path = "../exercises/23_conversions/conversions4.rs" },
|
||||
{ name = "conversions4_sol", path = "../solutions/23_conversions/conversions4.rs" },
|
||||
{ name = "conversions5", path = "../exercises/23_conversions/conversions5.rs" },
|
||||
{ name = "conversions5_sol", path = "../solutions/23_conversions/conversions5.rs" },
|
||||
]
|
||||
|
||||
[package]
|
||||
name = "exercises"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
# Don't publish the exercises on crates.io!
|
||||
publish = false
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Variables
|
||||
|
||||
In Rust, variables are immutable by default.
|
||||
When a variable is immutable, once a value is bound to a name, you can’t change that value.
|
||||
When a variable is immutable, once a value is bound to a name, you can't change that value.
|
||||
You can make them mutable by adding `mut` in front of the variable name.
|
||||
|
||||
## Further information
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
fn main() {
|
||||
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.
|
||||
number = 3;
|
||||
|
||||
@@ -19,7 +19,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn yummy_food() {
|
||||
// This means that calling `picky_eater` with the argument "food" should return "Yummy!".
|
||||
// This means that calling `picky_eater` with the argument "strawberry" should return "Yummy!".
|
||||
assert_eq!(picky_eater("strawberry"), "Yummy!");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
fn array_and_vec() -> ([i32; 4], Vec<i32>) {
|
||||
let a = [10, 20, 30, 40]; // Array
|
||||
|
||||
// TODO: Create a vector called `v` which contains the exact same elements as in the array `a`.
|
||||
// Use the vector macro.
|
||||
// let v = ???;
|
||||
|
||||
(a, v)
|
||||
fn elems_to_vec(a: i32, b: i32, c: i32) -> Vec<i32> {
|
||||
// TODO: Return a vector containing the elements a, b and c (in this order).
|
||||
// Use the "vec!" macro.
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@@ -17,8 +12,11 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_array_and_vec_similarity() {
|
||||
let (a, v) = array_and_vec();
|
||||
assert_eq!(a, *v);
|
||||
fn test_elems_to_vec() {
|
||||
let (a, b, c) = (2, 7, 12);
|
||||
let v = elems_to_vec(a, b, c);
|
||||
assert_eq!(v[0], a);
|
||||
assert_eq!(v[1], b);
|
||||
assert_eq!(v[2], c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,26 +9,6 @@ fn vec_loop(input: &[i32]) -> Vec<i32> {
|
||||
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() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
@@ -43,18 +23,4 @@ mod tests {
|
||||
let ans = vec_loop(&input);
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,28 @@
|
||||
// Structs contain data, but can also have logic. In this exercise, we have
|
||||
// defined the `Package` struct, and we want to test some logic attached to it.
|
||||
// defined the `Fireworks` struct and a couple of functions that work with it.
|
||||
// Turn these free-standing functions into methods and associated functions
|
||||
// to express that relationship more clearly in the code.
|
||||
|
||||
#![deny(clippy::use_self)] // practice using the `Self` type
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Package {
|
||||
sender_country: String,
|
||||
recipient_country: String,
|
||||
weight_in_grams: u32,
|
||||
struct Fireworks {
|
||||
rockets: usize,
|
||||
}
|
||||
|
||||
impl Package {
|
||||
fn new(sender_country: String, recipient_country: String, weight_in_grams: u32) -> Self {
|
||||
if weight_in_grams < 10 {
|
||||
// This isn't how you should handle errors in Rust, but we will
|
||||
// learn about error handling later.
|
||||
panic!("Can't ship a package with weight below 10 grams");
|
||||
}
|
||||
// TODO: Turn this function into an associated function on `Fireworks`.
|
||||
fn new_fireworks() -> Fireworks {
|
||||
Fireworks { rockets: 0 }
|
||||
}
|
||||
|
||||
Self {
|
||||
sender_country,
|
||||
recipient_country,
|
||||
weight_in_grams,
|
||||
}
|
||||
}
|
||||
// TODO: Turn this function into a method on `Fireworks`.
|
||||
fn add_rockets(fireworks: &mut Fireworks, rockets: usize) {
|
||||
fireworks.rockets += rockets
|
||||
}
|
||||
|
||||
// TODO: Add the correct return type to the function signature.
|
||||
fn is_international(&self) {
|
||||
// TODO: Read the tests that use this method to find out when a package
|
||||
// is considered international.
|
||||
}
|
||||
|
||||
// TODO: Add the correct return type to the function signature.
|
||||
fn get_fees(&self, cents_per_gram: u32) {
|
||||
// TODO: Calculate the package's fees.
|
||||
}
|
||||
// TODO: Turn this function into a method on `Fireworks`.
|
||||
fn start(fireworks: Fireworks) -> String {
|
||||
"🚀".repeat(fireworks.rockets)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@@ -44,44 +34,18 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn fail_creating_weightless_package() {
|
||||
let sender_country = String::from("Spain");
|
||||
let recipient_country = String::from("Austria");
|
||||
fn start_some_fireworks() {
|
||||
let f = Fireworks::new();
|
||||
assert_eq!(f.start(), "");
|
||||
|
||||
Package::new(sender_country, recipient_country, 5);
|
||||
}
|
||||
let mut f = Fireworks::new();
|
||||
f.add_rockets(3);
|
||||
assert_eq!(f.start(), "🚀🚀🚀");
|
||||
|
||||
#[test]
|
||||
fn create_international_package() {
|
||||
let sender_country = String::from("Spain");
|
||||
let recipient_country = String::from("Russia");
|
||||
|
||||
let package = Package::new(sender_country, recipient_country, 1200);
|
||||
|
||||
assert!(package.is_international());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_local_package() {
|
||||
let sender_country = String::from("Canada");
|
||||
let recipient_country = sender_country.clone();
|
||||
|
||||
let package = Package::new(sender_country, recipient_country, 1200);
|
||||
|
||||
assert!(!package.is_international());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn calculate_transport_fees() {
|
||||
let sender_country = String::from("Spain");
|
||||
let recipient_country = String::from("Spain");
|
||||
|
||||
let cents_per_gram = 3;
|
||||
|
||||
let package = Package::new(sender_country, recipient_country, 1500);
|
||||
|
||||
assert_eq!(package.get_fees(cents_per_gram), 4500);
|
||||
assert_eq!(package.get_fees(cents_per_gram * 2), 9000);
|
||||
let mut f = Fireworks::new();
|
||||
f.add_rockets(7);
|
||||
// We don't use method syntax in the last test to ensure the `start`
|
||||
// function takes ownership of the fireworks.
|
||||
assert_eq!(Fireworks::start(f), "🚀🚀🚀🚀🚀🚀🚀");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Enums
|
||||
|
||||
Rust allows you to define types called "enums" which enumerate possible values.
|
||||
Enums are a feature in many languages, but their capabilities differ in each language. Rust’s enums are most similar to algebraic data types in functional languages, such as F#, OCaml, and Haskell.
|
||||
Enums are a feature in many languages, but their capabilities differ in each language. Rust's enums are most similar to algebraic data types in functional languages, such as F#, OCaml, and Haskell.
|
||||
Useful in combination with enums is Rust's "pattern matching" facility, which makes it easy to run different code for different values of an enumeration.
|
||||
|
||||
## Further information
|
||||
|
||||
- [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)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# Strings
|
||||
|
||||
Rust has two string types, a string slice (`&str`) and an owned string (`String`).
|
||||
Rust has two string types: a string slice (`&str`) and an owned string (`String`).
|
||||
We're not going to dictate when you should use which one, but we'll show you how
|
||||
to identify and create them, as well as use them.
|
||||
|
||||
## Further information
|
||||
|
||||
- [Strings](https://doc.rust-lang.org/book/ch08-02-strings.html)
|
||||
- [Strings (Rust Book)](https://doc.rust-lang.org/book/ch08-02-strings.html)
|
||||
- [`str` methods](https://doc.rust-lang.org/std/primitive.str.html)
|
||||
- [`String` methods](https://doc.rust-lang.org/std/string/struct.String.html)
|
||||
|
||||
@@ -23,6 +23,7 @@ mod tests {
|
||||
assert_eq!(trim_me("Hello! "), "Hello!");
|
||||
assert_eq!(trim_me(" What's up!"), "What's up!");
|
||||
assert_eq!(trim_me(" Hola! "), "Hola!");
|
||||
assert_eq!(trim_me("Hi!"), "Hi!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -21,8 +21,6 @@ fn main() {
|
||||
|
||||
placeholder("rust is fun!".to_owned());
|
||||
|
||||
placeholder("nice weather".into());
|
||||
|
||||
placeholder(format!("Interpolation {}", "Station"));
|
||||
|
||||
// WARNING: This is byte indexing, not character indexing.
|
||||
|
||||
@@ -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,
|
||||
// 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.
|
||||
fn maybe_icecream(hour_of_day: u16) -> Option<u16> {
|
||||
fn maybe_ice_cream(hour_of_day: u16) -> Option<u16> {
|
||||
// TODO: Complete the function body.
|
||||
}
|
||||
|
||||
@@ -18,19 +18,19 @@ mod tests {
|
||||
fn raw_value() {
|
||||
// TODO: Fix this test. How do you get the value contained in the
|
||||
// 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]
|
||||
fn check_icecream() {
|
||||
assert_eq!(maybe_icecream(0), Some(5));
|
||||
assert_eq!(maybe_icecream(9), Some(5));
|
||||
assert_eq!(maybe_icecream(18), Some(5));
|
||||
assert_eq!(maybe_icecream(22), Some(0));
|
||||
assert_eq!(maybe_icecream(23), Some(0));
|
||||
assert_eq!(maybe_icecream(24), None);
|
||||
assert_eq!(maybe_icecream(25), None);
|
||||
fn check_ice_cream() {
|
||||
assert_eq!(maybe_ice_cream(0), Some(5));
|
||||
assert_eq!(maybe_ice_cream(9), Some(5));
|
||||
assert_eq!(maybe_ice_cream(18), Some(5));
|
||||
assert_eq!(maybe_ice_cream(22), Some(0));
|
||||
assert_eq!(maybe_ice_cream(23), Some(0));
|
||||
assert_eq!(maybe_ice_cream(24), None);
|
||||
assert_eq!(maybe_ice_cream(25), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ fn main() {
|
||||
|
||||
// TODO: Fix the compiler error by adding something to this match statement.
|
||||
match optional_point {
|
||||
Some(p) => println!("Co-ordinates are {},{}", p.x, p.y),
|
||||
Some(p) => println!("Coordinates are {},{}", p.x, p.y),
|
||||
_ => panic!("No match!"),
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Error handling
|
||||
|
||||
Most errors aren’t serious enough to require the program to stop entirely.
|
||||
Sometimes, when a function fails, it’s for a reason that you can easily interpret and respond to.
|
||||
For example, if you try to open a file and that operation fails because the file doesn’t exist, you might want to create the file instead of terminating the process.
|
||||
Most errors aren't serious enough to require the program to stop entirely.
|
||||
Sometimes, when a function fails, it's for a reason that you can easily interpret and respond to.
|
||||
For example, if you try to open a file and that operation fails because the file doesn't exist, you might want to create the file instead of terminating the process.
|
||||
|
||||
## Further information
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ struct PositiveNonzeroInteger(u64);
|
||||
impl PositiveNonzeroInteger {
|
||||
fn new(value: i64) -> Result<Self, CreationError> {
|
||||
// TODO: This function shouldn't always return an `Ok`.
|
||||
// Read the tests below to clarify what should be returned.
|
||||
Ok(Self(value as u64))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//
|
||||
// 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
|
||||
// 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
|
||||
// context. For this exercise, that context is the potential errors which
|
||||
// can be returned in a `Result`.
|
||||
|
||||
@@ -19,15 +19,6 @@ enum ParsePosNonzeroError {
|
||||
ParseInt(ParseIntError),
|
||||
}
|
||||
|
||||
impl ParsePosNonzeroError {
|
||||
fn from_creation(err: CreationError) -> Self {
|
||||
Self::Creation(err)
|
||||
}
|
||||
|
||||
// TODO: Add another error conversion function here.
|
||||
// fn from_parse_int(???) -> Self { ??? }
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct PositiveNonzeroInteger(u64);
|
||||
|
||||
@@ -44,7 +35,7 @@ impl PositiveNonzeroInteger {
|
||||
// TODO: change this to return an appropriate error instead of panicking
|
||||
// when `parse()` returns an error.
|
||||
let x: i64 = s.parse().unwrap();
|
||||
Self::new(x).map_err(ParsePosNonzeroError::from_creation)
|
||||
Self::new(x).map_err(ParsePosNonzeroError::Creation)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ fn main() {
|
||||
mod tests {
|
||||
#[test]
|
||||
fn iterators() {
|
||||
let my_fav_fruits = ["banana", "custard apple", "avocado", "peach", "raspberry"];
|
||||
let my_fav_fruits = &["banana", "custard apple", "avocado", "peach", "raspberry"];
|
||||
|
||||
// TODO: Create an iterator over the array.
|
||||
// TODO: Create an iterator over the slice.
|
||||
let mut fav_fruits_iterator = todo!();
|
||||
|
||||
assert_eq!(fav_fruits_iterator.next(), Some(&"banana"));
|
||||
|
||||
@@ -39,6 +39,8 @@ mod tests {
|
||||
#[test]
|
||||
fn test_success() {
|
||||
assert_eq!(divide(81, 9), Ok(9));
|
||||
assert_eq!(divide(81, -1), Ok(-81));
|
||||
assert_eq!(divide(i64::MIN, i64::MIN), Ok(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -10,5 +10,6 @@ of exercises to Rustlings, but is all about learning to write Macros.
|
||||
|
||||
## 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/)
|
||||
- [Rust by Example - macro_rules!](https://doc.rust-lang.org/rust-by-example/macros.html)
|
||||
|
||||
@@ -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.
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[allow(unused_variables, unused_assignments)]
|
||||
fn main() {
|
||||
let my_option: Option<&str> = None;
|
||||
@@ -11,14 +10,16 @@ fn main() {
|
||||
println!("{}", my_option.unwrap());
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
let my_arr = &[
|
||||
-1, -2, -3
|
||||
-4, -5, -6
|
||||
];
|
||||
println!("My array! Here it is: {my_arr:?}");
|
||||
|
||||
let my_empty_vec = vec![1, 2, 3, 4, 5].resize(0, 5);
|
||||
println!("This Vec is empty, see? {my_empty_vec:?}");
|
||||
let mut my_vec = vec![1, 2, 3, 4, 5];
|
||||
my_vec.resize(0, 5);
|
||||
println!("This Vec is empty, see? {my_vec:?}");
|
||||
|
||||
let mut value_a = 45;
|
||||
let mut value_b = 66;
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
Rust offers a multitude of ways to convert a value of a given type into another type.
|
||||
|
||||
The simplest form of type conversion is a type cast expression. It is denoted with the binary operator `as`. For instance, `println!("{}", 1 + 1.0);` would not compile, since `1` is an integer while `1.0` is a float. However, `println!("{}", 1 as f32 + 1.0)` should compile. The exercise [`using_as`](using_as.rs) tries to cover this.
|
||||
The simplest form of type conversion is a type cast expression. It is denoted with the binary operator `as`. For instance, `println!("{}", 1 + 1.0);` would not compile, since `1` is an integer while `1.0` is a float. However, `println!("{}", 1 as f32 + 1.0)` should compile. The exercise [`conversions1`](conversions1.rs) tries to cover this.
|
||||
|
||||
Rust also offers traits that facilitate type conversions upon implementation. These traits can be found under the [`convert`](https://doc.rust-lang.org/std/convert/index.html) module.
|
||||
The traits are the following:
|
||||
|
||||
- `From` and `Into` covered in [`from_into`](from_into.rs)
|
||||
- `TryFrom` and `TryInto` covered in [`try_from_into`](try_from_into.rs)
|
||||
- `AsRef` and `AsMut` covered in [`as_ref_mut`](as_ref_mut.rs)
|
||||
- `From` and `Into` covered in [`conversions2`](conversions2.rs)
|
||||
- `TryFrom` and `TryInto` covered in [`conversions4`](conversions4.rs)
|
||||
- `AsRef` and `AsMut` covered in [`conversions5`](conversions5.rs)
|
||||
|
||||
Furthermore, the `std::str` module offers a trait called [`FromStr`](https://doc.rust-lang.org/std/str/trait.FromStr.html) which helps with converting strings into target types via the `parse` method on strings. If properly implemented for a given type `Person`, then `let p: Person = "Mark,20".parse().unwrap()` should both compile and run without panicking.
|
||||
|
||||
|
||||
54
exercises/23_conversions/conversions2.rs
Normal file
54
exercises/23_conversions/conversions2.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
// The `From` trait is used for value-to-value conversions. If `From` is
|
||||
// implemented, an implementation of `Into` is automatically provided.
|
||||
// You can read more about it in the documentation:
|
||||
// https://doc.rust-lang.org/std/convert/trait.From.html
|
||||
//
|
||||
// Representing units of measurements with separate types is a common practice.
|
||||
// It avoids accidentally mixing up values of different units of measurement.
|
||||
|
||||
struct Celsius(f64);
|
||||
|
||||
struct Fahrenheit(f64);
|
||||
|
||||
impl From<Celsius> for Fahrenheit {
|
||||
// TODO: Convert Celsius to Fahrenheit. Don't worry about floating-point
|
||||
// precision. The formula is: F = C * 1.8 + 32
|
||||
}
|
||||
|
||||
impl From<Fahrenheit> for Celsius {
|
||||
// TODO: Convert Fahrenheit to Celsius.
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const CASES: [(f64, f64); 6] = [
|
||||
(-50.0, -58.0),
|
||||
(0.0, 32.0),
|
||||
(20.0, 68.0),
|
||||
(100.0, 212.0),
|
||||
(400.0, 752.0),
|
||||
(1000.0, 1832.0),
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn celsius_to_fahrenheit() {
|
||||
for (celsius, fahrenheit) in CASES {
|
||||
let Fahrenheit(actual) = Celsius(celsius).into();
|
||||
assert_eq!(actual.round(), fahrenheit);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fahrenheit_to_celsius() {
|
||||
for (celsius, fahrenheit) in CASES {
|
||||
let Celsius(actual) = Fahrenheit(fahrenheit).into();
|
||||
assert_eq!(actual.round(), celsius);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
// The `From` trait is used for value-to-value conversions. If `From` is
|
||||
// implemented, an implementation of `Into` is automatically provided.
|
||||
// You can read more about it in the documentation:
|
||||
// https://doc.rust-lang.org/std/convert/trait.From.html
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Person {
|
||||
name: String,
|
||||
age: u8,
|
||||
}
|
||||
|
||||
// We implement the Default trait to use it as a fallback when the provided
|
||||
// string is not convertible into a `Person` object.
|
||||
impl Default for Person {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: String::from("John"),
|
||||
age: 30,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Complete this `From` implementation to be able to parse a `Person`
|
||||
// out of a string in the form of "Mark,20".
|
||||
// Note that you'll need to parse the age component into a `u8` with something
|
||||
// like `"4".parse::<u8>()`.
|
||||
//
|
||||
// Steps:
|
||||
// 1. Split the given string on the commas present in it.
|
||||
// 2. If the split operation returns less or more than 2 elements, return the
|
||||
// default of `Person`.
|
||||
// 3. Use the first element from the split operation as the name.
|
||||
// 4. If the name is empty, return the default of `Person`.
|
||||
// 5. Parse the second element from the split operation into a `u8` as the age.
|
||||
// 6. If parsing the age fails, return the default of `Person`.
|
||||
impl From<&str> for Person {
|
||||
fn from(s: &str) -> Self {}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Use the `from` function.
|
||||
let p1 = Person::from("Mark,20");
|
||||
println!("{p1:?}");
|
||||
|
||||
// Since `From` is implemented for Person, we are able to use `Into`.
|
||||
let p2: Person = "Gerald,70".into();
|
||||
println!("{p2:?}");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_default() {
|
||||
let dp = Person::default();
|
||||
assert_eq!(dp.name, "John");
|
||||
assert_eq!(dp.age, 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bad_convert() {
|
||||
let p = Person::from("");
|
||||
assert_eq!(p.name, "John");
|
||||
assert_eq!(p.age, 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_good_convert() {
|
||||
let p = Person::from("Mark,20");
|
||||
assert_eq!(p.name, "Mark");
|
||||
assert_eq!(p.age, 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bad_age() {
|
||||
let p = Person::from("Mark,twenty");
|
||||
assert_eq!(p.name, "John");
|
||||
assert_eq!(p.age, 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_missing_comma_and_age() {
|
||||
let p: Person = Person::from("Mark");
|
||||
assert_eq!(p.name, "John");
|
||||
assert_eq!(p.age, 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_missing_age() {
|
||||
let p: Person = Person::from("Mark,");
|
||||
assert_eq!(p.name, "John");
|
||||
assert_eq!(p.age, 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_missing_name() {
|
||||
let p: Person = Person::from(",1");
|
||||
assert_eq!(p.name, "John");
|
||||
assert_eq!(p.age, 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_missing_name_and_age() {
|
||||
let p: Person = Person::from(",");
|
||||
assert_eq!(p.name, "John");
|
||||
assert_eq!(p.age, 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_missing_name_and_invalid_age() {
|
||||
let p: Person = Person::from(",one");
|
||||
assert_eq!(p.name, "John");
|
||||
assert_eq!(p.age, 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trailing_comma() {
|
||||
let p: Person = Person::from("Mike,32,");
|
||||
assert_eq!(p.name, "John");
|
||||
assert_eq!(p.age, 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trailing_comma_and_some_string() {
|
||||
let p: Person = Person::from("Mike,32,dog");
|
||||
assert_eq!(p.name, "John");
|
||||
assert_eq!(p.age, 30);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
| vecs | §8.1 |
|
||||
| move_semantics | §4.1-2 |
|
||||
| structs | §5.1, §5.3 |
|
||||
| enums | §6, §18.3 |
|
||||
| enums | §6, §19.3 |
|
||||
| strings | §8.2 |
|
||||
| modules | §7 |
|
||||
| hashmaps | §8.3 |
|
||||
@@ -22,6 +22,6 @@
|
||||
| iterators | §13.2-4 |
|
||||
| smart_pointers | §15, §16.3 |
|
||||
| threads | §16.1-3 |
|
||||
| macros | §19.5 |
|
||||
| clippy | §21.4 |
|
||||
| macros | §20.5 |
|
||||
| clippy | Appendix D |
|
||||
| conversions | n/a |
|
||||
|
||||
13
oranda.json
13
oranda.json
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"project": {
|
||||
"homepage": "https://rustlings.cool",
|
||||
"repository": "https://github.com/rust-lang/rustlings"
|
||||
},
|
||||
"marketing": {
|
||||
"analytics": {
|
||||
"plausible": {
|
||||
"domain": "rustlings.cool"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,12 @@
|
||||
set -e
|
||||
|
||||
typos
|
||||
cargo upgrades
|
||||
|
||||
# Similar to CI
|
||||
cargo clippy -- --deny warnings
|
||||
cargo fmt --all --check
|
||||
cargo test --workspace --all-targets
|
||||
cargo run -- dev check --require-solutions
|
||||
cargo test --workspace
|
||||
cargo dev check --require-solutions
|
||||
|
||||
# MSRV
|
||||
cargo +1.80 run -- dev check --require-solutions
|
||||
cargo +1.88 dev check --require-solutions
|
||||
|
||||
@@ -16,9 +16,9 @@ include = [
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
quote = "1.0.37"
|
||||
quote = "1"
|
||||
serde.workspace = true
|
||||
toml_edit.workspace = true
|
||||
toml.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -21,7 +21,7 @@ get started, here are some notes about how Rustlings operates:
|
||||
|
||||
final_message = """
|
||||
We hope you enjoyed learning about the various aspects of Rust!
|
||||
If you noticed any issues, don't hesitate to report them on Github.
|
||||
If you noticed any issues, don't hesitate to report them on GitHub.
|
||||
You can also contribute your own exercises to help the greater community!
|
||||
|
||||
Before reporting an issue or contributing, please read our guidelines:
|
||||
@@ -265,10 +265,10 @@ for `a.len() >= 100`?"""
|
||||
name = "primitive_types4"
|
||||
dir = "04_primitive_types"
|
||||
hint = """
|
||||
Take a look at the 'Understanding Ownership -> Slices -> Other Slices' section
|
||||
of the book: https://doc.rust-lang.org/book/ch04-03-slices.html and use the
|
||||
starting and ending (plus one) indices of the items in the array that you want
|
||||
to end up in the slice.
|
||||
Take a look at the 'Understanding Ownership -> The Slice Type -> Other Slices'
|
||||
section of the book: https://doc.rust-lang.org/book/ch04-03-slices.html and use
|
||||
the starting and ending (plus one) indices of the items in the array that you
|
||||
want to end up in the slice.
|
||||
|
||||
If you're curious why the first argument of `assert_eq!` does not have an
|
||||
ampersand for a reference since the second argument is a reference, take a look
|
||||
@@ -318,16 +318,7 @@ of the Rust book to learn more."""
|
||||
name = "vecs2"
|
||||
dir = "05_vecs"
|
||||
hint = """
|
||||
In the first function, we create an empty vector and want to push new elements
|
||||
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?"""
|
||||
Use the `.push()` method on the vector to push new elements to it."""
|
||||
|
||||
# MOVE SEMANTICS
|
||||
|
||||
@@ -426,11 +417,10 @@ https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-
|
||||
name = "structs3"
|
||||
dir = "07_structs"
|
||||
hint = """
|
||||
For `is_international`: What makes a package international? Seems related to
|
||||
the places it goes through right?
|
||||
|
||||
For `get_fees`: This method takes an additional argument, is there a field in
|
||||
the `Package` struct that this relates to?
|
||||
Methods and associated functions are both declared in an `impl MyType {}`
|
||||
block. Methods have a `self`, `&self` or `&mut self` parameter, where `self`
|
||||
implicitly has the type of the impl block. Associated functions do not have
|
||||
a `self` parameter.
|
||||
|
||||
Have a look in The Book to find out more about method implementations:
|
||||
https://doc.rust-lang.org/book/ch05-03-method-syntax.html"""
|
||||
@@ -448,8 +438,14 @@ name = "enums2"
|
||||
dir = "08_enums"
|
||||
test = false
|
||||
hint = """
|
||||
You can create enumerations that have different variants with different types
|
||||
such as anonymous structs, structs, a single string, tuples, no data, etc."""
|
||||
Enum variants can be defined using three different forms: struct-, tuple- and
|
||||
unit-like. Here's an example enum definition, which uses all three forms:
|
||||
|
||||
enum EnumUsingAllVariantForms {
|
||||
StructLike { named_field: bool },
|
||||
TupleLike(bool),
|
||||
UnitLike,
|
||||
}"""
|
||||
|
||||
[[exercises]]
|
||||
name = "enums3"
|
||||
@@ -764,7 +760,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,
|
||||
the implementation can take `mut self` instead. This is possible because the
|
||||
the value is owned anyway."""
|
||||
value is owned anyway."""
|
||||
|
||||
[[exercises]]
|
||||
name = "traits3"
|
||||
@@ -963,7 +959,7 @@ a different method that could make your code more compact than using `fold`."""
|
||||
# SMART POINTERS
|
||||
|
||||
[[exercises]]
|
||||
name = "box1"
|
||||
name = "smart_pointers1"
|
||||
dir = "19_smart_pointers"
|
||||
hint = """
|
||||
The compiler's message should help: Since we cannot store the value of the
|
||||
@@ -980,7 +976,7 @@ Although the current list is one of integers (`i32`), feel free to change the
|
||||
definition and try other types!"""
|
||||
|
||||
[[exercises]]
|
||||
name = "rc1"
|
||||
name = "smart_pointers2"
|
||||
dir = "19_smart_pointers"
|
||||
hint = """
|
||||
This is a straightforward exercise to use the `Rc<T>` type. Each `Planet` has
|
||||
@@ -997,7 +993,7 @@ See more at: https://doc.rust-lang.org/book/ch15-04-rc.html
|
||||
Unfortunately, Pluto is no longer considered a planet :("""
|
||||
|
||||
[[exercises]]
|
||||
name = "arc1"
|
||||
name = "smart_pointers3"
|
||||
dir = "19_smart_pointers"
|
||||
test = false
|
||||
hint = """
|
||||
@@ -1014,7 +1010,7 @@ Book:
|
||||
https://doc.rust-lang.org/book/ch16-00-concurrency.html"""
|
||||
|
||||
[[exercises]]
|
||||
name = "cow1"
|
||||
name = "smart_pointers4"
|
||||
dir = "19_smart_pointers"
|
||||
hint = """
|
||||
If `Cow` already owns the data, it doesn't need to clone it when `to_mut()` is
|
||||
@@ -1165,20 +1161,26 @@ hint = "No hints this time!"
|
||||
# TYPE CONVERSIONS
|
||||
|
||||
[[exercises]]
|
||||
name = "using_as"
|
||||
name = "conversions1"
|
||||
dir = "23_conversions"
|
||||
hint = """
|
||||
Use the `as` operator to cast one of the operands in the last line of the
|
||||
`average` function into the expected return type."""
|
||||
|
||||
[[exercises]]
|
||||
name = "from_into"
|
||||
name = "conversions2"
|
||||
dir = "23_conversions"
|
||||
hint = """
|
||||
Follow the steps provided right before the `From` implementation."""
|
||||
The formula for converting from Fahrenheit to Celsius is: C = (F - 32) / 1.8
|
||||
This can be derived from the first formula:
|
||||
|
||||
F = C * 1.8 + 32 // now subtract 32 on both sides
|
||||
F - 32 = C * 1.8 // then divide by 1.8
|
||||
(F - 32) / 1.8 = C
|
||||
"""
|
||||
|
||||
[[exercises]]
|
||||
name = "from_str"
|
||||
name = "conversions3"
|
||||
dir = "23_conversions"
|
||||
hint = """
|
||||
The implementation of `FromStr` should return an `Ok` with a `Person` object,
|
||||
@@ -1195,7 +1197,7 @@ operator in your solution, you might want to look at
|
||||
https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/reenter_question_mark.html"""
|
||||
|
||||
[[exercises]]
|
||||
name = "try_from_into"
|
||||
name = "conversions4"
|
||||
dir = "23_conversions"
|
||||
hint = """
|
||||
Is there an implementation of `TryFrom` in the standard library that can both do
|
||||
@@ -1205,7 +1207,7 @@ Challenge: Can you make the `TryFrom` implementations generic over many integer
|
||||
types?"""
|
||||
|
||||
[[exercises]]
|
||||
name = "as_ref_mut"
|
||||
name = "conversions5"
|
||||
dir = "23_conversions"
|
||||
hint = """
|
||||
Add `AsRef<str>` or `AsMut<u32>` as a trait bound to the functions."""
|
||||
|
||||
@@ -3,20 +3,29 @@ use quote::quote;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ExerciseInfo {
|
||||
name: String,
|
||||
dir: String,
|
||||
struct ExerciseInfo<'a> {
|
||||
name: &'a str,
|
||||
dir: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct InfoFile {
|
||||
exercises: Vec<ExerciseInfo>,
|
||||
struct InfoFile<'a> {
|
||||
#[serde(borrow)]
|
||||
exercises: Vec<ExerciseInfo<'a>>,
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn include_files(_: TokenStream) -> TokenStream {
|
||||
let info_file = include_str!("../info.toml");
|
||||
let exercises = toml_edit::de::from_str::<InfoFile>(info_file)
|
||||
// Remove `\r` on Windows
|
||||
let info_file = String::from_utf8(
|
||||
include_bytes!("../info.toml")
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|c| *c != b'\r')
|
||||
.collect(),
|
||||
)
|
||||
.expect("Failed to parse `info.toml` as UTF8");
|
||||
let exercises = toml::de::from_str::<InfoFile>(&info_file)
|
||||
.expect("Failed to parse `info.toml`")
|
||||
.exercises;
|
||||
|
||||
@@ -37,7 +46,7 @@ pub fn include_files(_: TokenStream) -> TokenStream {
|
||||
continue;
|
||||
}
|
||||
|
||||
dirs.push(exercise.dir.as_str());
|
||||
dirs.push(exercise.dir);
|
||||
*dir_ind = dirs.len() - 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
fn main() {
|
||||
let number = "T-H-R-E-E";
|
||||
println!("Spell a number: {}", number);
|
||||
println!("Spell a number: {number}");
|
||||
|
||||
// Using variable shadowing
|
||||
// https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#shadowing
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
fn bigger(a: i32, b: i32) -> i32 {
|
||||
if a > b {
|
||||
a
|
||||
} else {
|
||||
b
|
||||
}
|
||||
if a > b { a } else { b }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
@@ -2,6 +2,7 @@ fn animal_habitat(animal: &str) -> &str {
|
||||
let identifier = if animal == "crab" {
|
||||
1
|
||||
} else if animal == "gopher" {
|
||||
// Integer, so that every branch has the same type.
|
||||
2
|
||||
} else if animal == "snake" {
|
||||
3
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
fn array_and_vec() -> ([i32; 4], Vec<i32>) {
|
||||
let a = [10, 20, 30, 40]; // Array
|
||||
|
||||
// Used the `vec!` macro.
|
||||
let v = vec![10, 20, 30, 40];
|
||||
|
||||
(a, v)
|
||||
fn elems_to_vec(a: i32, b: i32, c: i32) -> Vec<i32> {
|
||||
vec![a, b, c]
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@@ -16,8 +11,11 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_array_and_vec_similarity() {
|
||||
let (a, v) = array_and_vec();
|
||||
assert_eq!(a, *v);
|
||||
fn test_elems_to_vec() {
|
||||
let (a, b, c) = (2, 7, 12);
|
||||
let v = elems_to_vec(a, b, c);
|
||||
assert_eq!(v[0], a);
|
||||
assert_eq!(v[1], b);
|
||||
assert_eq!(v[2], c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,22 +8,6 @@ fn vec_loop(input: &[i32]) -> Vec<i32> {
|
||||
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() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
@@ -38,18 +22,4 @@ mod tests {
|
||||
let ans = vec_loop(&input);
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ fn main() {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// TODO: Fix the compiler errors only by reordering the lines in the test.
|
||||
// Don't add, change or remove any line.
|
||||
#[test]
|
||||
fn move_semantics4() {
|
||||
let mut x = Vec::new();
|
||||
|
||||
@@ -1,33 +1,21 @@
|
||||
#![deny(clippy::use_self)] // practice using the `Self` type
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Package {
|
||||
sender_country: String,
|
||||
recipient_country: String,
|
||||
weight_in_grams: u32,
|
||||
struct Fireworks {
|
||||
rockets: usize,
|
||||
}
|
||||
|
||||
impl Package {
|
||||
fn new(sender_country: String, recipient_country: String, weight_in_grams: u32) -> Self {
|
||||
if weight_in_grams < 10 {
|
||||
// This isn't how you should handle errors in Rust, but we will
|
||||
// learn about error handling later.
|
||||
panic!("Can't ship a package with weight below 10 grams");
|
||||
}
|
||||
|
||||
Self {
|
||||
sender_country,
|
||||
recipient_country,
|
||||
weight_in_grams,
|
||||
}
|
||||
impl Fireworks {
|
||||
fn new() -> Self {
|
||||
Self { rockets: 0 }
|
||||
}
|
||||
|
||||
fn is_international(&self) -> bool {
|
||||
// ^^^^^^^ added
|
||||
self.sender_country != self.recipient_country
|
||||
fn add_rockets(&mut self, rockets: usize) {
|
||||
self.rockets += rockets
|
||||
}
|
||||
|
||||
fn get_fees(&self, cents_per_gram: u32) -> u32 {
|
||||
// ^^^^^^ added
|
||||
self.weight_in_grams * cents_per_gram
|
||||
fn start(self) -> String {
|
||||
"🚀".repeat(self.rockets)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,44 +28,18 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn fail_creating_weightless_package() {
|
||||
let sender_country = String::from("Spain");
|
||||
let recipient_country = String::from("Austria");
|
||||
fn start_some_fireworks() {
|
||||
let f = Fireworks::new();
|
||||
assert_eq!(f.start(), "");
|
||||
|
||||
Package::new(sender_country, recipient_country, 5);
|
||||
}
|
||||
let mut f = Fireworks::new();
|
||||
f.add_rockets(3);
|
||||
assert_eq!(f.start(), "🚀🚀🚀");
|
||||
|
||||
#[test]
|
||||
fn create_international_package() {
|
||||
let sender_country = String::from("Spain");
|
||||
let recipient_country = String::from("Russia");
|
||||
|
||||
let package = Package::new(sender_country, recipient_country, 1200);
|
||||
|
||||
assert!(package.is_international());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_local_package() {
|
||||
let sender_country = String::from("Canada");
|
||||
let recipient_country = sender_country.clone();
|
||||
|
||||
let package = Package::new(sender_country, recipient_country, 1200);
|
||||
|
||||
assert!(!package.is_international());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn calculate_transport_fees() {
|
||||
let sender_country = String::from("Spain");
|
||||
let recipient_country = String::from("Spain");
|
||||
|
||||
let cents_per_gram = 3;
|
||||
|
||||
let package = Package::new(sender_country, recipient_country, 1500);
|
||||
|
||||
assert_eq!(package.get_fees(cents_per_gram), 4500);
|
||||
assert_eq!(package.get_fees(cents_per_gram * 2), 9000);
|
||||
let mut f = Fireworks::new();
|
||||
f.add_rockets(7);
|
||||
// We don't use method syntax in the last test to ensure the `start`
|
||||
// function takes ownership of the fireworks.
|
||||
assert_eq!(Fireworks::start(f), "🚀🚀🚀🚀🚀🚀🚀");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ mod tests {
|
||||
assert_eq!(trim_me("Hello! "), "Hello!");
|
||||
assert_eq!(trim_me(" What's up!"), "What's up!");
|
||||
assert_eq!(trim_me(" Hola! "), "Hola!");
|
||||
assert_eq!(trim_me("Hi!"), "Hi!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -15,15 +15,6 @@ fn main() {
|
||||
|
||||
string("rust is fun!".to_owned());
|
||||
|
||||
// Here, both answers work.
|
||||
// `.into()` converts a type into an expected type.
|
||||
// If it is called where `String` is expected, it will convert `&str` to `String`.
|
||||
string("nice weather".into());
|
||||
// But if it is called where `&str` is expected, then `&str` is kept `&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.
|
||||
#[allow(clippy::useless_conversion)]
|
||||
string_slice("nice weather".into());
|
||||
|
||||
string(format!("Interpolation {}", "Station"));
|
||||
|
||||
// WARNING: This is byte indexing, not character indexing.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 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
|
||||
// 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.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -60,9 +60,11 @@ England,Spain,1,0";
|
||||
fn build_scores() {
|
||||
let scores = build_scores_table(RESULTS);
|
||||
|
||||
assert!(["England", "France", "Germany", "Italy", "Poland", "Spain"]
|
||||
.into_iter()
|
||||
.all(|team_name| scores.contains_key(team_name)));
|
||||
assert!(
|
||||
["England", "France", "Germany", "Italy", "Poland", "Spain"]
|
||||
.into_iter()
|
||||
.all(|team_name| scores.contains_key(team_name))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -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,
|
||||
// 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.
|
||||
fn maybe_icecream(hour_of_day: u16) -> Option<u16> {
|
||||
fn maybe_ice_cream(hour_of_day: u16) -> Option<u16> {
|
||||
match hour_of_day {
|
||||
0..=21 => Some(5),
|
||||
22..=23 => Some(0),
|
||||
@@ -21,19 +21,19 @@ mod tests {
|
||||
#[test]
|
||||
fn raw_value() {
|
||||
// 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]
|
||||
fn check_icecream() {
|
||||
assert_eq!(maybe_icecream(0), Some(5));
|
||||
assert_eq!(maybe_icecream(9), Some(5));
|
||||
assert_eq!(maybe_icecream(18), Some(5));
|
||||
assert_eq!(maybe_icecream(22), Some(0));
|
||||
assert_eq!(maybe_icecream(23), Some(0));
|
||||
assert_eq!(maybe_icecream(24), None);
|
||||
assert_eq!(maybe_icecream(25), None);
|
||||
fn check_ice_cream() {
|
||||
assert_eq!(maybe_ice_cream(0), Some(5));
|
||||
assert_eq!(maybe_ice_cream(9), Some(5));
|
||||
assert_eq!(maybe_ice_cream(18), Some(5));
|
||||
assert_eq!(maybe_ice_cream(22), Some(0));
|
||||
assert_eq!(maybe_ice_cream(23), Some(0));
|
||||
assert_eq!(maybe_ice_cream(24), None);
|
||||
assert_eq!(maybe_ice_cream(25), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ fn main() {
|
||||
// Solution 1: Matching over the `Option` (not `&Option`) but without moving
|
||||
// out of the `Some` variant.
|
||||
match optional_point {
|
||||
Some(ref p) => println!("Co-ordinates are {},{}", p.x, p.y),
|
||||
Some(ref p) => println!("Coordinates are {},{}", p.x, p.y),
|
||||
// ^^^ added
|
||||
_ => panic!("No match!"),
|
||||
}
|
||||
@@ -18,7 +18,8 @@ fn main() {
|
||||
// Solution 2: Matching over a reference (`&Option`) by added `&` before
|
||||
// `optional_point`.
|
||||
match &optional_point {
|
||||
Some(p) => println!("Co-ordinates are {},{}", p.x, p.y),
|
||||
//^ added
|
||||
Some(p) => println!("Coordinates are {},{}", p.x, p.y),
|
||||
_ => panic!("No match!"),
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
use std::num::ParseIntError;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[allow(unused_variables, clippy::question_mark)]
|
||||
fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> {
|
||||
let processing_fee = 1;
|
||||
let cost_per_item = 5;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//
|
||||
// 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
|
||||
// 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
|
||||
// context. For this exercise, that context is the potential errors which
|
||||
// can be returned in a `Result`.
|
||||
|
||||
@@ -19,16 +19,6 @@ enum ParsePosNonzeroError {
|
||||
ParseInt(ParseIntError),
|
||||
}
|
||||
|
||||
impl ParsePosNonzeroError {
|
||||
fn from_creation(err: CreationError) -> Self {
|
||||
Self::Creation(err)
|
||||
}
|
||||
|
||||
fn from_parse_int(err: ParseIntError) -> Self {
|
||||
Self::ParseInt(err)
|
||||
}
|
||||
}
|
||||
|
||||
// As an alternative solution, implementing the `From` trait allows for the
|
||||
// automatic conversion from a `ParseIntError` into a `ParsePosNonzeroError`
|
||||
// using the `?` operator, without the need to call `map_err`.
|
||||
@@ -59,9 +49,9 @@ impl PositiveNonzeroInteger {
|
||||
fn parse(s: &str) -> Result<Self, ParsePosNonzeroError> {
|
||||
// Return an appropriate error instead of panicking when `parse()`
|
||||
// returns an error.
|
||||
let x: i64 = s.parse().map_err(ParsePosNonzeroError::from_parse_int)?;
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Self::new(x).map_err(ParsePosNonzeroError::from_creation)
|
||||
let x: i64 = s.parse().map_err(ParsePosNonzeroError::ParseInt)?;
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Self::new(x).map_err(ParsePosNonzeroError::Creation)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,7 @@
|
||||
|
||||
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
|
||||
// ^^^^ ^^ ^^ ^^
|
||||
if x.len() > y.len() {
|
||||
x
|
||||
} else {
|
||||
y
|
||||
}
|
||||
if x.len() > y.len() { x } else { y }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
|
||||
if x.len() > y.len() {
|
||||
x
|
||||
} else {
|
||||
y
|
||||
}
|
||||
if x.len() > y.len() { x } else { y }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
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.
|
||||
let string2 = String::from("xyz");
|
||||
let result;
|
||||
@@ -25,7 +21,7 @@ fn main() {
|
||||
{
|
||||
let string2 = String::from("xyz");
|
||||
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.
|
||||
println!("The longest string is '{result}'");
|
||||
// `string2` dropped here (end of the inner scope).
|
||||
|
||||
@@ -10,9 +10,9 @@ fn main() {
|
||||
mod tests {
|
||||
#[test]
|
||||
fn iterators() {
|
||||
let my_fav_fruits = ["banana", "custard apple", "avocado", "peach", "raspberry"];
|
||||
let my_fav_fruits = &["banana", "custard apple", "avocado", "peach", "raspberry"];
|
||||
|
||||
// Create an iterator over the array.
|
||||
// Create an iterator over the slice.
|
||||
let mut fav_fruits_iterator = my_fav_fruits.iter();
|
||||
|
||||
assert_eq!(fav_fruits_iterator.next(), Some(&"banana"));
|
||||
|
||||
@@ -52,6 +52,8 @@ mod tests {
|
||||
#[test]
|
||||
fn test_success() {
|
||||
assert_eq!(divide(81, 9), Ok(9));
|
||||
assert_eq!(divide(81, -1), Ok(-81));
|
||||
assert_eq!(divide(i64::MIN, i64::MIN), Ok(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -63,12 +63,10 @@ mod tests {
|
||||
println!("reference count = {}", Rc::strong_count(&sun)); // 7 references
|
||||
saturn.details();
|
||||
|
||||
// TODO
|
||||
let uranus = Planet::Uranus(Rc::clone(&sun));
|
||||
println!("reference count = {}", Rc::strong_count(&sun)); // 8 references
|
||||
uranus.details();
|
||||
|
||||
// TODO
|
||||
let neptune = Planet::Neptune(Rc::clone(&sun));
|
||||
println!("reference count = {}", Rc::strong_count(&sun)); // 9 references
|
||||
neptune.details();
|
||||
@@ -1,4 +1,4 @@
|
||||
// Added the attribute `macro_use` attribute.
|
||||
// Added the `macro_use` attribute.
|
||||
#[macro_use]
|
||||
mod macros {
|
||||
macro_rules! my_macro {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::mem;
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[allow(unused_variables, unused_assignments)]
|
||||
fn main() {
|
||||
let my_option: Option<&str> = None;
|
||||
@@ -11,21 +10,22 @@ fn main() {
|
||||
}
|
||||
|
||||
// A comma was missing.
|
||||
#[rustfmt::skip]
|
||||
let my_arr = &[
|
||||
-1, -2, -3,
|
||||
-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(0, …)` clears a vector, so it is better to use `clear`.
|
||||
my_empty_vec.clear();
|
||||
println!("This Vec is empty, see? {my_empty_vec:?}");
|
||||
my_vec.clear();
|
||||
println!("This Vec is empty, see? {my_vec:?}");
|
||||
|
||||
let mut value_a = 45;
|
||||
let mut value_b = 66;
|
||||
// Use `mem::swap` to correctly swap two values.
|
||||
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}");
|
||||
}
|
||||
|
||||
57
solutions/23_conversions/conversions2.rs
Normal file
57
solutions/23_conversions/conversions2.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
// The `From` trait is used for value-to-value conversions. If `From` is
|
||||
// implemented, an implementation of `Into` is automatically provided.
|
||||
// You can read more about it in the documentation:
|
||||
// https://doc.rust-lang.org/std/convert/trait.From.html
|
||||
//
|
||||
// Representing units of measurements with separate types is a common practice.
|
||||
// It avoids accidentally mixing up values of different units of measurement.
|
||||
|
||||
struct Celsius(f64);
|
||||
|
||||
struct Fahrenheit(f64);
|
||||
|
||||
impl From<Celsius> for Fahrenheit {
|
||||
fn from(Celsius(celsius): Celsius) -> Self {
|
||||
Fahrenheit(celsius * 1.8 + 32.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Fahrenheit> for Celsius {
|
||||
fn from(Fahrenheit(fahrenheit): Fahrenheit) -> Self {
|
||||
Celsius((fahrenheit - 32.0) / 1.8)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const CASES: [(f64, f64); 6] = [
|
||||
(-50.0, -58.0),
|
||||
(0.0, 32.0),
|
||||
(20.0, 68.0),
|
||||
(100.0, 212.0),
|
||||
(400.0, 752.0),
|
||||
(1000.0, 1832.0),
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn celsius_to_fahrenheit() {
|
||||
for (celsius, fahrenheit) in CASES {
|
||||
let Fahrenheit(actual) = Celsius(celsius).into();
|
||||
assert_eq!(actual.round(), fahrenheit);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fahrenheit_to_celsius() {
|
||||
for (celsius, fahrenheit) in CASES {
|
||||
let Celsius(actual) = Fahrenheit(fahrenheit).into();
|
||||
assert_eq!(actual.round(), celsius);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,13 +52,12 @@ impl TryFrom<&[i16]> for Color {
|
||||
type Error = IntoColorError;
|
||||
|
||||
fn try_from(slice: &[i16]) -> Result<Self, Self::Error> {
|
||||
// Check the length.
|
||||
if slice.len() != 3 {
|
||||
return Err(IntoColorError::BadLen);
|
||||
if let &[red, green, blue] = slice {
|
||||
// Reuse the implementation for a tuple.
|
||||
Self::try_from((red, green, blue))
|
||||
} else {
|
||||
Err(IntoColorError::BadLen)
|
||||
}
|
||||
|
||||
// Reuse the implementation for a tuple.
|
||||
Self::try_from((slice[0], slice[1], slice[2]))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
// The `From` trait is used for value-to-value conversions. If `From` is
|
||||
// implemented, an implementation of `Into` is automatically provided.
|
||||
// You can read more about it in the documentation:
|
||||
// https://doc.rust-lang.org/std/convert/trait.From.html
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Person {
|
||||
name: String,
|
||||
age: u8,
|
||||
}
|
||||
|
||||
// We implement the Default trait to use it as a fallback when the provided
|
||||
// string is not convertible into a `Person` object.
|
||||
impl Default for Person {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: String::from("John"),
|
||||
age: 30,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Person {
|
||||
fn from(s: &str) -> Self {
|
||||
let mut split = s.split(',');
|
||||
let (Some(name), Some(age), None) = (split.next(), split.next(), split.next()) else {
|
||||
// ^^^^ there should be no third element
|
||||
return Self::default();
|
||||
};
|
||||
|
||||
if name.is_empty() {
|
||||
return Self::default();
|
||||
}
|
||||
|
||||
let Ok(age) = age.parse() else {
|
||||
return Self::default();
|
||||
};
|
||||
|
||||
Self {
|
||||
name: name.into(),
|
||||
age,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Use the `from` function.
|
||||
let p1 = Person::from("Mark,20");
|
||||
println!("{p1:?}");
|
||||
|
||||
// Since `From` is implemented for Person, we are able to use `Into`.
|
||||
let p2: Person = "Gerald,70".into();
|
||||
println!("{p2:?}");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_default() {
|
||||
let dp = Person::default();
|
||||
assert_eq!(dp.name, "John");
|
||||
assert_eq!(dp.age, 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bad_convert() {
|
||||
let p = Person::from("");
|
||||
assert_eq!(p.name, "John");
|
||||
assert_eq!(p.age, 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_good_convert() {
|
||||
let p = Person::from("Mark,20");
|
||||
assert_eq!(p.name, "Mark");
|
||||
assert_eq!(p.age, 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bad_age() {
|
||||
let p = Person::from("Mark,twenty");
|
||||
assert_eq!(p.name, "John");
|
||||
assert_eq!(p.age, 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_missing_comma_and_age() {
|
||||
let p: Person = Person::from("Mark");
|
||||
assert_eq!(p.name, "John");
|
||||
assert_eq!(p.age, 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_missing_age() {
|
||||
let p: Person = Person::from("Mark,");
|
||||
assert_eq!(p.name, "John");
|
||||
assert_eq!(p.age, 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_missing_name() {
|
||||
let p: Person = Person::from(",1");
|
||||
assert_eq!(p.name, "John");
|
||||
assert_eq!(p.age, 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_missing_name_and_age() {
|
||||
let p: Person = Person::from(",");
|
||||
assert_eq!(p.name, "John");
|
||||
assert_eq!(p.age, 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_missing_name_and_invalid_age() {
|
||||
let p: Person = Person::from(",one");
|
||||
assert_eq!(p.name, "John");
|
||||
assert_eq!(p.age, 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trailing_comma() {
|
||||
let p: Person = Person::from("Mike,32,");
|
||||
assert_eq!(p.name, "John");
|
||||
assert_eq!(p.age, 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trailing_comma_and_some_string() {
|
||||
let p: Person = Person::from("Mike,32,dog");
|
||||
assert_eq!(p.name, "John");
|
||||
assert_eq!(p.age, 30);
|
||||
}
|
||||
}
|
||||
@@ -62,8 +62,8 @@ mod tests {
|
||||
// Import `transformer`.
|
||||
use super::my_module::transformer;
|
||||
|
||||
use super::my_module::transformer_iter;
|
||||
use super::Command;
|
||||
use super::my_module::transformer_iter;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
|
||||
197
src/app_state.rs
197
src/app_state.rs
@@ -1,11 +1,10 @@
|
||||
use anyhow::{bail, Context, Error, Result};
|
||||
use crossterm::{cursor, terminal, QueueableCommand};
|
||||
use anyhow::{Context, Error, Result, bail};
|
||||
use crossterm::{QueueableCommand, cursor, terminal};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
env,
|
||||
fs::{File, OpenOptions},
|
||||
io::{Read, Seek, StdoutLock, Write},
|
||||
path::{Path, MAIN_SEPARATOR_STR},
|
||||
path::{MAIN_SEPARATOR_STR, Path},
|
||||
process::{Command, Stdio},
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering::Relaxed},
|
||||
@@ -17,6 +16,7 @@ use std::{
|
||||
use crate::{
|
||||
clear_terminal,
|
||||
cmd::CmdRunner,
|
||||
editor::{Editor, EditorJoinHandle},
|
||||
embedded::EMBEDDED_FILES,
|
||||
exercise::{Exercise, RunnableExercise},
|
||||
info_file::ExerciseInfo,
|
||||
@@ -52,22 +52,24 @@ pub enum CheckProgress {
|
||||
pub struct AppState {
|
||||
current_exercise_ind: usize,
|
||||
exercises: Vec<Exercise>,
|
||||
// Caches the number of done exercises to avoid iterating over all exercises every time.
|
||||
n_done: u16,
|
||||
final_message: String,
|
||||
// Cache the number of done exercises to avoid iterating over all exercises every time.
|
||||
n_done: u32,
|
||||
final_message: &'static str,
|
||||
state_file: File,
|
||||
// Preallocated buffer for reading and writing the state file.
|
||||
file_buf: Vec<u8>,
|
||||
official_exercises: bool,
|
||||
cmd_runner: CmdRunner,
|
||||
// Running in VS Code.
|
||||
vs_code: bool,
|
||||
emit_file_links: bool,
|
||||
editor: Option<Editor>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn new(
|
||||
exercise_infos: Vec<ExerciseInfo>,
|
||||
final_message: String,
|
||||
final_message: &'static str,
|
||||
editor: Option<Editor>,
|
||||
vs_code_term: bool,
|
||||
) -> Result<(Self, StateFileStatus)> {
|
||||
let cmd_runner = CmdRunner::build()?;
|
||||
let mut state_file = OpenOptions::new()
|
||||
@@ -84,43 +86,38 @@ impl AppState {
|
||||
let mut exercises = exercise_infos
|
||||
.into_iter()
|
||||
.map(|exercise_info| {
|
||||
// Leaking to be able to borrow in the watch mode `Table`.
|
||||
// Leaking is not a problem because the `AppState` instance lives until
|
||||
// the end of the program.
|
||||
let path = exercise_info.path().leak();
|
||||
let name = exercise_info.name.leak();
|
||||
let dir = exercise_info.dir.map(|dir| &*dir.leak());
|
||||
let hint = exercise_info.hint.leak().trim_ascii();
|
||||
|
||||
let canonical_path = dir_canonical_path.as_deref().map(|dir_canonical_path| {
|
||||
let mut canonical_path;
|
||||
if let Some(dir) = dir {
|
||||
if let Some(dir) = exercise_info.dir {
|
||||
canonical_path = String::with_capacity(
|
||||
2 + dir_canonical_path.len() + dir.len() + name.len(),
|
||||
2 + dir_canonical_path.len() + dir.len() + exercise_info.name.len(),
|
||||
);
|
||||
canonical_path.push_str(dir_canonical_path);
|
||||
canonical_path.push_str(MAIN_SEPARATOR_STR);
|
||||
canonical_path.push_str(dir);
|
||||
} else {
|
||||
canonical_path =
|
||||
String::with_capacity(1 + dir_canonical_path.len() + name.len());
|
||||
canonical_path = String::with_capacity(
|
||||
1 + dir_canonical_path.len() + exercise_info.name.len(),
|
||||
);
|
||||
canonical_path.push_str(dir_canonical_path);
|
||||
}
|
||||
|
||||
canonical_path.push_str(MAIN_SEPARATOR_STR);
|
||||
canonical_path.push_str(name);
|
||||
canonical_path.push_str(exercise_info.name);
|
||||
canonical_path.push_str(".rs");
|
||||
canonical_path
|
||||
});
|
||||
|
||||
Exercise {
|
||||
dir,
|
||||
name,
|
||||
path,
|
||||
name: exercise_info.name,
|
||||
dir: exercise_info.dir,
|
||||
// Leaking for `Editor::open`.
|
||||
// Leaking is fine since the app state exists until the end of the program.
|
||||
path: exercise_info.path().leak(),
|
||||
canonical_path,
|
||||
test: exercise_info.test,
|
||||
strict_clippy: exercise_info.strict_clippy,
|
||||
hint,
|
||||
hint: exercise_info.hint.trim_ascii(),
|
||||
// Updated below.
|
||||
done: false,
|
||||
}
|
||||
@@ -181,45 +178,40 @@ impl AppState {
|
||||
file_buf,
|
||||
official_exercises: !Path::new("info.toml").exists(),
|
||||
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: !vs_code_term,
|
||||
editor,
|
||||
};
|
||||
|
||||
Ok((slf, state_file_status))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn current_exercise_ind(&self) -> usize {
|
||||
self.current_exercise_ind
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn exercises(&self) -> &[Exercise] {
|
||||
&self.exercises
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn n_done(&self) -> u16 {
|
||||
pub fn n_done(&self) -> u32 {
|
||||
self.n_done
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn n_pending(&self) -> u16 {
|
||||
self.exercises.len() as u16 - self.n_done
|
||||
pub fn n_pending(&self) -> u32 {
|
||||
self.exercises.len() as u32 - self.n_done
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn current_exercise(&self) -> &Exercise {
|
||||
&self.exercises[self.current_exercise_ind]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cmd_runner(&self) -> &CmdRunner {
|
||||
&self.cmd_runner
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn vs_code(&self) -> bool {
|
||||
self.vs_code
|
||||
pub fn emit_file_links(&self) -> bool {
|
||||
self.emit_file_links
|
||||
}
|
||||
|
||||
// Write the state file.
|
||||
@@ -315,7 +307,7 @@ impl AppState {
|
||||
}
|
||||
|
||||
// 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<()> {
|
||||
if self.official_exercises {
|
||||
return EMBEDDED_FILES
|
||||
@@ -343,12 +335,10 @@ impl AppState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn reset_current_exercise(&mut self) -> Result<&'static str> {
|
||||
pub fn reset_current_exercise(&mut self) -> Result<()> {
|
||||
self.set_pending(self.current_exercise_ind)?;
|
||||
let exercise = self.current_exercise();
|
||||
self.reset(self.current_exercise_ind, exercise.path)?;
|
||||
|
||||
Ok(exercise.path)
|
||||
self.reset(self.current_exercise_ind, exercise.path)
|
||||
}
|
||||
|
||||
// Reset the exercise by index and return its name.
|
||||
@@ -385,7 +375,7 @@ impl AppState {
|
||||
}
|
||||
|
||||
/// 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>> {
|
||||
if cfg!(debug_assertions) {
|
||||
return Ok(None);
|
||||
@@ -427,32 +417,34 @@ impl AppState {
|
||||
let next_exercise_ind = &next_exercise_ind;
|
||||
let slf = &self;
|
||||
thread::Builder::new()
|
||||
.spawn_scoped(s, move || loop {
|
||||
let exercise_ind = next_exercise_ind.fetch_add(1, Relaxed);
|
||||
let Some(exercise) = slf.exercises.get(exercise_ind) else {
|
||||
// No more exercises.
|
||||
break;
|
||||
};
|
||||
.spawn_scoped(s, move || {
|
||||
loop {
|
||||
let exercise_ind = next_exercise_ind.fetch_add(1, Relaxed);
|
||||
let Some(exercise) = slf.exercises.get(exercise_ind) else {
|
||||
// No more exercises.
|
||||
break;
|
||||
};
|
||||
|
||||
if exercise_progress_sender
|
||||
.send((exercise_ind, CheckProgress::Checking))
|
||||
.is_err()
|
||||
{
|
||||
break;
|
||||
};
|
||||
if exercise_progress_sender
|
||||
.send((exercise_ind, CheckProgress::Checking))
|
||||
.is_err()
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
let success = exercise.run_exercise(None, &slf.cmd_runner);
|
||||
let progress = match success {
|
||||
Ok(true) => CheckProgress::Done,
|
||||
Ok(false) => CheckProgress::Pending,
|
||||
Err(_) => CheckProgress::None,
|
||||
};
|
||||
let success = exercise.run_exercise(None, &slf.cmd_runner);
|
||||
let progress = match success {
|
||||
Ok(true) => CheckProgress::Done,
|
||||
Ok(false) => CheckProgress::Pending,
|
||||
Err(_) => CheckProgress::None,
|
||||
};
|
||||
|
||||
if exercise_progress_sender
|
||||
.send((exercise_ind, progress))
|
||||
.is_err()
|
||||
{
|
||||
break;
|
||||
if exercise_progress_sender
|
||||
.send((exercise_ind, progress))
|
||||
.is_err()
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.context("Failed to spawn a thread to check all exercises")?;
|
||||
@@ -555,7 +547,7 @@ impl AppState {
|
||||
|
||||
pub fn render_final_message(&self, stdout: &mut StdoutLock) -> Result<()> {
|
||||
clear_terminal(stdout)?;
|
||||
stdout.write_all(FENISH_LINE.as_bytes())?;
|
||||
stdout.write_all(FINISH_LINE.as_bytes())?;
|
||||
|
||||
let final_message = self.final_message.trim_ascii();
|
||||
if !final_message.is_empty() {
|
||||
@@ -565,29 +557,51 @@ impl AppState {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn open_editor(&mut self) -> Result<EditorJoinHandle> {
|
||||
if let Some(editor) = self.editor.take() {
|
||||
return editor.open(self.current_exercise_ind, self.current_exercise().path);
|
||||
}
|
||||
|
||||
Ok(EditorJoinHandle::default())
|
||||
}
|
||||
|
||||
pub fn join_editor_handle(&mut self, handle: EditorJoinHandle) -> Result<()> {
|
||||
self.editor = handle.join()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn close_editor(&mut self) -> Result<()> {
|
||||
if let Some(editor) = &mut self.editor {
|
||||
editor.close()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
const BAD_INDEX_ERR: &str = "The current exercise index is higher than the number of exercises";
|
||||
const STATE_FILE_HEADER: &[u8] = b"DON'T EDIT THIS FILE!\n\n";
|
||||
const FENISH_LINE: &str = "+----------------------------------------------------+
|
||||
| You made it to the Fe-nish line! |
|
||||
const FINISH_LINE: &str = "+----------------------------------------------------+
|
||||
| You made it to the finish line! |
|
||||
+-------------------------- ------------------------+
|
||||
\\/\x1b[31m
|
||||
▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒
|
||||
▒▒▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒▒▒
|
||||
▒▒▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒▒▒
|
||||
░░▒▒▒▒░░▒▒ ▒▒ ▒▒ ▒▒ ▒▒░░▒▒▒▒
|
||||
▓▓▓▓▓▓▓▓ ▓▓ ▓▓██ ▓▓ ▓▓██ ▓▓ ▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒ ▒▒ ████ ▒▒ ████ ▒▒░░ ▒▒▒▒
|
||||
▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒
|
||||
▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▒▒▒▒▒▒▒▒▓▓▓▓▓▓▒▒▒▒▒▒▒▒
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||
▒▒▒▒▒▒▒▒▒▒██▒▒▒▒▒▒██▒▒▒▒▒▒▒▒▒▒
|
||||
▒▒ ▒▒▒▒▒▒▒▒▒▒██████▒▒▒▒▒▒▒▒▒▒ ▒▒
|
||||
▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒
|
||||
▒▒ ▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒ ▒▒
|
||||
▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒
|
||||
▒▒ ▒▒ ▒▒ ▒▒\x1b[0m
|
||||
\\/\x1b[31m
|
||||
▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒
|
||||
▒▒▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒▒▒
|
||||
▒▒▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒▒▒
|
||||
▒▒▒▒░░▒▒ ▒▒ ▒▒ ▒▒ ▒▒░░▒▒▒▒
|
||||
▓▓▓▓▓▓▓▓ ▓▓ ▓▓██ ▓▓ ▓▓██ ▓▓ ▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒ ▒▒ ████ ▒▒ ████ ▒▒ ▒▒▒▒
|
||||
▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒
|
||||
▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▒▒▒▒▒▒▒▒▓▓▓▓▓▓▒▒▒▒▒▒▒▒
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||
▒▒▒▒▒▒▒▒▒▒██▒▒▒▒▒▒██▒▒▒▒▒▒▒▒▒▒
|
||||
▒▒ ▒▒▒▒▒▒▒▒▒▒██████▒▒▒▒▒▒▒▒▒▒ ▒▒
|
||||
▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒
|
||||
▒▒ ▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒ ▒▒
|
||||
▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒
|
||||
▒▒ ▒▒ ▒▒ ▒▒\x1b[0m
|
||||
|
||||
";
|
||||
|
||||
@@ -597,8 +611,8 @@ mod tests {
|
||||
|
||||
fn dummy_exercise() -> Exercise {
|
||||
Exercise {
|
||||
dir: None,
|
||||
name: "0",
|
||||
dir: None,
|
||||
path: "exercises/0.rs",
|
||||
canonical_path: None,
|
||||
test: false,
|
||||
@@ -614,12 +628,13 @@ mod tests {
|
||||
current_exercise_ind: 0,
|
||||
exercises: vec![dummy_exercise(), dummy_exercise(), dummy_exercise()],
|
||||
n_done: 0,
|
||||
final_message: String::new(),
|
||||
final_message: "",
|
||||
state_file: tempfile::tempfile().unwrap(),
|
||||
file_buf: Vec::new(),
|
||||
official_exercises: true,
|
||||
cmd_runner: CmdRunner::build().unwrap(),
|
||||
vs_code: false,
|
||||
emit_file_links: true,
|
||||
editor: None,
|
||||
};
|
||||
|
||||
let mut assert = |done: [bool; 3], expected: [Option<usize>; 3]| {
|
||||
|
||||
@@ -38,7 +38,7 @@ pub fn append_bins(
|
||||
buf.extend_from_slice(b"\", path = \"");
|
||||
buf.extend_from_slice(exercise_path_prefix);
|
||||
buf.extend_from_slice(b"exercises/");
|
||||
if let Some(dir) = &exercise_info.dir {
|
||||
if let Some(dir) = exercise_info.dir {
|
||||
buf.extend_from_slice(dir.as_bytes());
|
||||
buf.push(b'/');
|
||||
}
|
||||
@@ -56,7 +56,7 @@ pub fn append_bins(
|
||||
buf.extend_from_slice(b"\", path = \"");
|
||||
buf.extend_from_slice(exercise_path_prefix);
|
||||
buf.extend_from_slice(b"solutions/");
|
||||
if let Some(dir) = &exercise_info.dir {
|
||||
if let Some(dir) = exercise_info.dir {
|
||||
buf.extend_from_slice(dir.as_bytes());
|
||||
buf.push(b'/');
|
||||
}
|
||||
@@ -74,13 +74,13 @@ pub fn updated_cargo_toml(
|
||||
let (bins_start_ind, bins_end_ind) = bins_start_end_ind(current_cargo_toml)?;
|
||||
|
||||
let mut updated_cargo_toml = Vec::with_capacity(BINS_BUFFER_CAPACITY);
|
||||
updated_cargo_toml.extend_from_slice(current_cargo_toml[..bins_start_ind].as_bytes());
|
||||
updated_cargo_toml.extend_from_slice(¤t_cargo_toml.as_bytes()[..bins_start_ind]);
|
||||
append_bins(
|
||||
&mut updated_cargo_toml,
|
||||
exercise_infos,
|
||||
exercise_path_prefix,
|
||||
);
|
||||
updated_cargo_toml.extend_from_slice(current_cargo_toml[bins_end_ind..].as_bytes());
|
||||
updated_cargo_toml.extend_from_slice(¤t_cargo_toml.as_bytes()[bins_end_ind..]);
|
||||
|
||||
Ok(updated_cargo_toml)
|
||||
}
|
||||
@@ -106,19 +106,19 @@ mod tests {
|
||||
fn test_bins() {
|
||||
let exercise_infos = [
|
||||
ExerciseInfo {
|
||||
name: String::from("1"),
|
||||
name: "1",
|
||||
dir: None,
|
||||
test: true,
|
||||
strict_clippy: true,
|
||||
hint: String::new(),
|
||||
hint: "",
|
||||
skip_check_unsolved: false,
|
||||
},
|
||||
ExerciseInfo {
|
||||
name: String::from("2"),
|
||||
dir: Some(String::from("d")),
|
||||
name: "2",
|
||||
dir: Some("d"),
|
||||
test: false,
|
||||
strict_clippy: false,
|
||||
hint: String::new(),
|
||||
hint: "",
|
||||
skip_check_unsolved: false,
|
||||
},
|
||||
];
|
||||
@@ -134,7 +134,14 @@ mod tests {
|
||||
);
|
||||
|
||||
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
|
||||
bin = [
|
||||
{ name = "1", path = "../exercises/1.rs" },
|
||||
|
||||
57
src/cli.rs
Normal file
57
src/cli.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
use crate::dev::DevCommand;
|
||||
|
||||
/// Rustlings is a collection of small exercises to get you used to writing and reading Rust code
|
||||
#[derive(Parser)]
|
||||
#[command(version)]
|
||||
pub struct Args {
|
||||
#[command(subcommand)]
|
||||
pub command: Option<Command>,
|
||||
/// Disable automatic opening of the current file in VS Code or Zellij.
|
||||
/// Ignores `--edit-cmd`
|
||||
#[arg(long)]
|
||||
pub no_editor: bool,
|
||||
/// Open the current exercise by running `EDIT_CMD EXERCISE_PATH`.
|
||||
/// The command is not allowed to block (e.g. `vim`).
|
||||
/// It should communicate with an editor in a different process.
|
||||
/// `EDIT_CMD` can contain arguments like `--edit-cmd "PROGRAM -x --arg1"`.
|
||||
/// The current exercise's path is added by Rustlings as the last argument.
|
||||
/// `--edit-cmd` is ignored in VS Code.
|
||||
///
|
||||
/// Example: `--edit-cmd "code"` (default behavior if running in a VS Code terminal)
|
||||
#[arg(long)]
|
||||
pub edit_cmd: Option<String>,
|
||||
/// Manually run the current exercise using `r` in the watch mode.
|
||||
/// Only use this if Rustlings fails to detect exercise file changes
|
||||
#[arg(long)]
|
||||
pub manual_run: bool,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum Command {
|
||||
/// Initialize the official Rustlings exercises
|
||||
Init,
|
||||
/// Run a single exercise.
|
||||
/// Runs the next pending exercise if the exercise name is not specified
|
||||
Run {
|
||||
/// The name of the exercise
|
||||
name: Option<String>,
|
||||
},
|
||||
/// Check all the exercises, marking them as done or pending accordingly
|
||||
CheckAll,
|
||||
/// Reset a single exercise
|
||||
Reset {
|
||||
/// The name of the exercise
|
||||
name: String,
|
||||
},
|
||||
/// Show a hint.
|
||||
/// Shows the hint of the next pending exercise if the exercise name is not specified
|
||||
Hint {
|
||||
/// The name of the exercise
|
||||
name: Option<String>,
|
||||
},
|
||||
/// Commands for developing (community) Rustlings exercises
|
||||
#[command(subcommand)]
|
||||
Dev(DevCommand),
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use anyhow::{bail, Context, Result};
|
||||
use anyhow::{Context, Result, bail};
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
io::Read,
|
||||
io::{Read, pipe},
|
||||
path::PathBuf,
|
||||
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 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}``")
|
||||
})?;
|
||||
|
||||
@@ -126,7 +126,6 @@ pub struct CargoSubcommand<'out> {
|
||||
}
|
||||
|
||||
impl CargoSubcommand<'_> {
|
||||
#[inline]
|
||||
pub fn args<'arg, I>(&mut self, args: I) -> &mut Self
|
||||
where
|
||||
I: IntoIterator<Item = &'arg str>,
|
||||
@@ -136,7 +135,6 @@ impl CargoSubcommand<'_> {
|
||||
}
|
||||
|
||||
/// The boolean in the returned `Result` is true if the command's exit status is success.
|
||||
#[inline]
|
||||
pub fn run(self, description: &str) -> Result<bool> {
|
||||
run_cmd(self.cmd, description, self.output)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use anyhow::{bail, Context, Result};
|
||||
use anyhow::{Context, Result, bail};
|
||||
use clap::Subcommand;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -7,8 +7,8 @@ mod new;
|
||||
mod update;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum DevCommands {
|
||||
/// Create a new project for third-party Rustlings exercises
|
||||
pub enum DevCommand {
|
||||
/// Create a new project for community exercises
|
||||
New {
|
||||
/// The path to create the project in
|
||||
path: PathBuf,
|
||||
@@ -26,7 +26,7 @@ pub enum DevCommands {
|
||||
Update,
|
||||
}
|
||||
|
||||
impl DevCommands {
|
||||
impl DevCommand {
|
||||
pub fn run(self) -> Result<()> {
|
||||
match self {
|
||||
Self::New { path, no_git } => {
|
||||
|
||||
114
src/dev/check.rs
114
src/dev/check.rs
@@ -1,8 +1,8 @@
|
||||
use anyhow::{anyhow, bail, Context, Error, Result};
|
||||
use anyhow::{Context, Error, Result, anyhow, bail};
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::HashSet,
|
||||
fs::{self, read_dir, OpenOptions},
|
||||
fs::{self, OpenOptions, read_dir},
|
||||
io::{self, Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
process::{Command, Stdio},
|
||||
@@ -10,11 +10,12 @@ use std::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
cargo_toml::{append_bins, bins_start_end_ind, BINS_BUFFER_CAPACITY},
|
||||
cmd::CmdRunner,
|
||||
exercise::{RunnableExercise, OUTPUT_CAPACITY},
|
||||
info_file::{ExerciseInfo, InfoFile},
|
||||
CURRENT_FORMAT_VERSION,
|
||||
cargo_toml::{BINS_BUFFER_CAPACITY, append_bins, bins_start_end_ind},
|
||||
cmd::CmdRunner,
|
||||
exercise::{OUTPUT_CAPACITY, RunnableExercise},
|
||||
info_file::{ExerciseInfo, InfoFile},
|
||||
term::ProgressCounter,
|
||||
};
|
||||
|
||||
const MAX_N_EXERCISES: usize = 999;
|
||||
@@ -42,10 +43,14 @@ fn check_cargo_toml(
|
||||
|
||||
if old_bins != new_bins {
|
||||
if cfg!(debug_assertions) {
|
||||
bail!("The file `dev/Cargo.toml` is outdated. Run `cargo run -- dev update` to update it. Then run `cargo run -- dev check` again");
|
||||
bail!(
|
||||
"The file `dev/Cargo.toml` is outdated. Run `cargo dev update` to update it. Then run `cargo run -- dev check` again"
|
||||
);
|
||||
}
|
||||
|
||||
bail!("The file `Cargo.toml` is outdated. Run `rustlings dev update` to update it. Then run `rustlings dev check` again");
|
||||
bail!(
|
||||
"The file `Cargo.toml` is outdated. Run `rustlings dev update` to update it. Then run `rustlings dev check` again"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -58,18 +63,20 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
|
||||
|
||||
let mut file_buf = String::with_capacity(1 << 14);
|
||||
for exercise_info in &info_file.exercises {
|
||||
let name = exercise_info.name.as_str();
|
||||
let name = exercise_info.name;
|
||||
if name.is_empty() {
|
||||
bail!("Found an empty exercise name in `info.toml`");
|
||||
}
|
||||
if name.len() > MAX_EXERCISE_NAME_LEN {
|
||||
bail!("The length of the exercise name `{name}` is bigger than the maximum {MAX_EXERCISE_NAME_LEN}");
|
||||
bail!(
|
||||
"The length of the exercise name `{name}` is bigger than the maximum {MAX_EXERCISE_NAME_LEN}"
|
||||
);
|
||||
}
|
||||
if let Some(c) = forbidden_char(name) {
|
||||
bail!("Char `{c}` in the exercise name `{name}` is not allowed");
|
||||
}
|
||||
|
||||
if let Some(dir) = &exercise_info.dir {
|
||||
if let Some(dir) = exercise_info.dir {
|
||||
if dir.is_empty() {
|
||||
bail!("The exercise `{name}` has an empty dir name in `info.toml`");
|
||||
}
|
||||
@@ -79,7 +86,9 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
|
||||
}
|
||||
|
||||
if exercise_info.hint.trim_ascii().is_empty() {
|
||||
bail!("The exercise `{name}` has an empty hint. Please provide a hint or at least tell the user why a hint isn't needed for this exercise");
|
||||
bail!(
|
||||
"The exercise `{name}` has an empty hint. Please provide a hint or at least tell the user why a hint isn't needed for this exercise"
|
||||
);
|
||||
}
|
||||
|
||||
if !names.insert(name) {
|
||||
@@ -96,20 +105,30 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
|
||||
.with_context(|| format!("Failed to read the file {path}"))?;
|
||||
|
||||
if !file_buf.contains("fn main()") {
|
||||
bail!("The `main` function is missing in the file `{path}`.\nCreate at least an empty `main` function to avoid language server errors");
|
||||
bail!(
|
||||
"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") {
|
||||
bail!("Didn't find any `// TODO` comment in the file `{path}`.\nYou need to have at least one such comment to guide the user.");
|
||||
bail!(
|
||||
"Didn't find any `// TODO` comment in the file `{path}`.\n\
|
||||
You need to have at least one such comment to guide the user."
|
||||
);
|
||||
}
|
||||
|
||||
let contains_tests = file_buf.contains("#[test]\n");
|
||||
if exercise_info.test {
|
||||
if !contains_tests {
|
||||
bail!("The file `{path}` doesn't contain any tests. If you don't want to add tests to this exercise, set `test = false` for this exercise in the `info.toml` file");
|
||||
bail!(
|
||||
"The file `{path}` doesn't contain any tests. If you don't want to add tests to this exercise, set `test = false` for this exercise in the `info.toml` file"
|
||||
);
|
||||
}
|
||||
} else if contains_tests {
|
||||
bail!("The file `{path}` contains tests annotated with `#[test]` but the exercise `{name}` has `test = false` in the `info.toml` file");
|
||||
bail!(
|
||||
"The file `{path}` contains tests annotated with `#[test]` but the exercise `{name}` has `test = false` in the `info.toml` file"
|
||||
);
|
||||
}
|
||||
|
||||
file_buf.clear();
|
||||
@@ -125,7 +144,10 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
|
||||
// Only one level of directory nesting is allowed.
|
||||
fn check_unexpected_files(dir: &str, allowed_rust_files: &HashSet<PathBuf>) -> Result<()> {
|
||||
let unexpected_file = |path: &Path| {
|
||||
anyhow!("Found the file `{}`. Only `README.md` and Rust files related to an exercise in `info.toml` are allowed in the `{dir}` directory", path.display())
|
||||
anyhow!(
|
||||
"Found the file `{}`. Only `README.md` and Rust files related to an exercise in `info.toml` are allowed in the `{dir}` directory",
|
||||
path.display()
|
||||
)
|
||||
};
|
||||
|
||||
for entry in read_dir(dir).with_context(|| format!("Failed to open the `{dir}` directory"))? {
|
||||
@@ -154,7 +176,10 @@ fn check_unexpected_files(dir: &str, allowed_rust_files: &HashSet<PathBuf>) -> R
|
||||
let path = entry.path();
|
||||
|
||||
if !entry.file_type().unwrap().is_file() {
|
||||
bail!("Found `{}` but expected only files. Only one level of exercise nesting is allowed", path.display());
|
||||
bail!(
|
||||
"Found `{}` but expected only files. Only one level of exercise nesting is allowed",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
|
||||
let file_name = path.file_name().unwrap();
|
||||
@@ -189,16 +214,13 @@ fn check_exercises_unsolved(
|
||||
Some(
|
||||
thread::Builder::new()
|
||||
.spawn(|| exercise_info.run_exercise(None, cmd_runner))
|
||||
.map(|handle| (exercise_info.name.as_str(), handle)),
|
||||
.map(|handle| (exercise_info.name, handle)),
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.context("Failed to spawn a thread to check if an exercise is already solved")?;
|
||||
|
||||
let n_handles = handles.len();
|
||||
write!(stdout, "Progress: 0/{n_handles}")?;
|
||||
stdout.flush()?;
|
||||
let mut handle_num = 1;
|
||||
let mut progress_counter = ProgressCounter::new(&mut stdout, handles.len())?;
|
||||
|
||||
for (exercise_name, handle) in handles {
|
||||
let Ok(result) = handle.join() else {
|
||||
@@ -207,25 +229,31 @@ fn check_exercises_unsolved(
|
||||
|
||||
match result {
|
||||
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) => (),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
|
||||
write!(stdout, "\rProgress: {handle_num}/{n_handles}")?;
|
||||
stdout.flush()?;
|
||||
handle_num += 1;
|
||||
progress_counter.increment()?;
|
||||
}
|
||||
stdout.write_all(b"\n")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_exercises(info_file: &'static InfoFile, cmd_runner: &'static CmdRunner) -> Result<()> {
|
||||
match info_file.format_version.cmp(&CURRENT_FORMAT_VERSION) {
|
||||
Ordering::Less => bail!("`format_version` < {CURRENT_FORMAT_VERSION} (supported version)\nPlease migrate to the latest format version"),
|
||||
Ordering::Greater => bail!("`format_version` > {CURRENT_FORMAT_VERSION} (supported version)\nTry updating the Rustlings program"),
|
||||
Ordering::Less => bail!(
|
||||
"`format_version` < {CURRENT_FORMAT_VERSION} (supported version)\n\
|
||||
Please migrate to the latest format version"
|
||||
),
|
||||
Ordering::Greater => bail!(
|
||||
"`format_version` > {CURRENT_FORMAT_VERSION} (supported version)\n\
|
||||
Try updating the Rustlings program"
|
||||
),
|
||||
Ordering::Equal => (),
|
||||
}
|
||||
|
||||
@@ -287,15 +315,12 @@ fn check_solutions(
|
||||
fmt_cmd
|
||||
.arg("--check")
|
||||
.arg("--edition")
|
||||
.arg("2021")
|
||||
.arg("2024")
|
||||
.arg("--color")
|
||||
.arg("always")
|
||||
.stdin(Stdio::null());
|
||||
|
||||
let n_handles = handles.len();
|
||||
write!(stdout, "Progress: 0/{n_handles}")?;
|
||||
stdout.flush()?;
|
||||
let mut handle_num = 1;
|
||||
let mut progress_counter = ProgressCounter::new(&mut stdout, handles.len())?;
|
||||
|
||||
for (exercise_info, handle) in info_file.exercises.iter().zip(handles) {
|
||||
let Ok(check_result) = handle.join() else {
|
||||
@@ -312,7 +337,7 @@ fn check_solutions(
|
||||
}
|
||||
SolutionCheck::MissingOptional => (),
|
||||
SolutionCheck::RunFailure { output } => {
|
||||
stdout.write_all(b"\n\n")?;
|
||||
drop(progress_counter);
|
||||
stdout.write_all(&output)?;
|
||||
bail!(
|
||||
"Running the solution of the exercise {} failed with the error above",
|
||||
@@ -322,22 +347,21 @@ fn check_solutions(
|
||||
SolutionCheck::Err(e) => return Err(e),
|
||||
}
|
||||
|
||||
write!(stdout, "\rProgress: {handle_num}/{n_handles}")?;
|
||||
stdout.flush()?;
|
||||
handle_num += 1;
|
||||
progress_counter.increment()?;
|
||||
}
|
||||
stdout.write_all(b"\n")?;
|
||||
|
||||
let n_solutions = sol_paths.len();
|
||||
let handle = thread::Builder::new()
|
||||
.spawn(move || check_unexpected_files("solutions", &sol_paths))
|
||||
.context(
|
||||
"Failed to spawn a thread to check for unexpected files in the solutions directory",
|
||||
)?;
|
||||
|
||||
if !fmt_cmd
|
||||
.status()
|
||||
.context("Failed to run `rustfmt` on all solution files")?
|
||||
.success()
|
||||
if n_solutions > 0
|
||||
&& !fmt_cmd
|
||||
.status()
|
||||
.context("Failed to run `rustfmt` on all solution files")?
|
||||
.success()
|
||||
{
|
||||
bail!("Some solutions aren't formatted. Run `rustfmt` on them");
|
||||
}
|
||||
@@ -353,7 +377,7 @@ pub fn check(require_solutions: bool) -> Result<()> {
|
||||
}
|
||||
|
||||
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"../")?;
|
||||
} else {
|
||||
check_cargo_toml(&info_file.exercises, "Cargo.toml", b"")?;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use anyhow::{bail, Context, Result};
|
||||
use anyhow::{Context, Result, bail};
|
||||
use std::{
|
||||
env::set_current_dir,
|
||||
fs::{self, create_dir},
|
||||
@@ -6,7 +6,7 @@ use std::{
|
||||
process::Command,
|
||||
};
|
||||
|
||||
use crate::{init::RUST_ANALYZER_TOML, CURRENT_FORMAT_VERSION};
|
||||
use crate::{CURRENT_FORMAT_VERSION, init::RUST_ANALYZER_TOML};
|
||||
|
||||
// Create a directory relative to the current directory and print its path.
|
||||
fn create_rel_dir(dir_name: &str, current_dir: &str) -> Result<()> {
|
||||
@@ -55,7 +55,9 @@ pub fn new(path: &Path, no_git: bool) -> Result<()> {
|
||||
write_rel_file(
|
||||
"info.toml",
|
||||
&dir_path_str,
|
||||
format!("{INFO_FILE_BEFORE_FORMAT_VERSION}{CURRENT_FORMAT_VERSION}{INFO_FILE_AFTER_FORMAT_VERSION}"),
|
||||
format!(
|
||||
"{INFO_FILE_BEFORE_FORMAT_VERSION}{CURRENT_FORMAT_VERSION}{INFO_FILE_AFTER_FORMAT_VERSION}"
|
||||
),
|
||||
)?;
|
||||
|
||||
write_rel_file("Cargo.toml", &dir_path_str, CARGO_TOML)?;
|
||||
@@ -76,18 +78,17 @@ pub fn new(path: &Path, no_git: bool) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub const GITIGNORE: &[u8] = b".rustlings-state.txt
|
||||
Cargo.lock
|
||||
pub const GITIGNORE: &[u8] = b"Cargo.lock
|
||||
target/
|
||||
.vscode/
|
||||
!.vscode/extensions.json
|
||||
";
|
||||
|
||||
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.
|
||||
# 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.
|
||||
# Otherwise, the newest version of the Rustlings program won't be able to run these exercises.
|
||||
format_version = ";
|
||||
@@ -95,7 +96,7 @@ format_version = ";
|
||||
const INFO_FILE_AFTER_FORMAT_VERSION: &str = r#"
|
||||
|
||||
# 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.
|
||||
final_message = """We hope that you found the exercises helpful :D"""
|
||||
@@ -130,7 +131,7 @@ bin = []
|
||||
|
||||
[package]
|
||||
name = "exercises"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
# Don't publish the exercises on crates.io!
|
||||
publish = false
|
||||
|
||||
@@ -139,7 +140,7 @@ publish = false
|
||||
|
||||
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) ✅
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ pub fn update() -> Result<()> {
|
||||
let info_file = InfoFile::parse()?;
|
||||
|
||||
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"../")
|
||||
.context("Failed to update the file `dev/Cargo.toml`")?;
|
||||
|
||||
|
||||
144
src/editor.rs
Normal file
144
src/editor.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
env,
|
||||
process::{Command, Stdio},
|
||||
thread::{self, JoinHandle},
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use shlex::Shlex;
|
||||
|
||||
mod zellij;
|
||||
|
||||
fn run_cmd(cmd: &mut Command) -> Result<Vec<u8>> {
|
||||
let output = cmd
|
||||
.stdin(Stdio::null())
|
||||
.output()
|
||||
.with_context(|| format!("Failed to run the command {cmd:?}"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
bail!(
|
||||
"The command {cmd:?} didn't run successfully\n\n\
|
||||
stdout:\n{}\n\n\
|
||||
stderr:\n{}",
|
||||
str::from_utf8(&output.stdout).unwrap_or_default(),
|
||||
str::from_utf8(&output.stderr).unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(output.stdout)
|
||||
}
|
||||
|
||||
fn program_exists(program: &str) -> bool {
|
||||
Command::new(program)
|
||||
.arg("--version")
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status()
|
||||
.is_ok_and(|status| status.success())
|
||||
}
|
||||
|
||||
pub enum Editor {
|
||||
Cmd(Cow<'static, str>, Vec<String>),
|
||||
Zellij(Option<(String, u32, usize)>),
|
||||
}
|
||||
|
||||
impl Editor {
|
||||
pub fn new(cmd: Option<String>, vs_code_term: bool) -> Result<Option<Self>> {
|
||||
if vs_code_term {
|
||||
for program in ["code", "codium"] {
|
||||
if program_exists(program) {
|
||||
return Ok(Some(Self::Cmd(Cow::Borrowed(program), Vec::new())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(cmd) = cmd {
|
||||
let shlex = &mut Shlex::new(&cmd);
|
||||
let program = shlex.next().context("Program missing in `--edit-cmd`")?;
|
||||
let args = shlex.collect();
|
||||
if shlex.had_error {
|
||||
bail!("Failed to parse the command in `--edit-cmd`");
|
||||
}
|
||||
return Ok(Some(Self::Cmd(Cow::Owned(program), args)));
|
||||
}
|
||||
|
||||
if env::var_os("ZELLIJ").is_some() && program_exists("zellij") {
|
||||
return Ok(Some(Self::Zellij(None)));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn open(
|
||||
mut self,
|
||||
exercise_ind: usize,
|
||||
exercise_path: &'static str,
|
||||
) -> Result<EditorJoinHandle> {
|
||||
let handle = thread::Builder::new()
|
||||
.spawn(move || {
|
||||
match &mut self {
|
||||
Editor::Cmd(program, args) => {
|
||||
run_cmd(Command::new(&**program).args(args).arg(exercise_path))?;
|
||||
}
|
||||
Editor::Zellij(open_pane) => {
|
||||
if let Some((pane_id_str, pane_id, open_exercise_ind)) = open_pane {
|
||||
if *open_exercise_ind == exercise_ind {
|
||||
if zellij::pane_open(*pane_id)? {
|
||||
return Ok(self);
|
||||
}
|
||||
} else {
|
||||
zellij::close_pane(pane_id_str)?;
|
||||
}
|
||||
}
|
||||
|
||||
let stdout = run_cmd(
|
||||
Command::new("zellij")
|
||||
.arg("action")
|
||||
.arg("edit")
|
||||
.arg(exercise_path),
|
||||
)?;
|
||||
|
||||
let (pane_id_str, pane_id) = zellij::parse_pane_id(&stdout)
|
||||
.context("Failed to parse the ID of the new Zellij pane")?;
|
||||
|
||||
*open_pane = Some((pane_id_str, pane_id, exercise_ind));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
})
|
||||
.context("Failed to spawn a thread to open the editor")?;
|
||||
|
||||
Ok(EditorJoinHandle(Some(handle)))
|
||||
}
|
||||
|
||||
pub fn close(&mut self) -> Result<()> {
|
||||
match self {
|
||||
Editor::Cmd(_, _) => (),
|
||||
Editor::Zellij(open_pane) => {
|
||||
if let Some((pane_id_str, _, _)) = open_pane.take() {
|
||||
zellij::close_pane(&pane_id_str)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[derive(Default)]
|
||||
pub struct EditorJoinHandle(Option<JoinHandle<Result<Editor>>>);
|
||||
|
||||
impl EditorJoinHandle {
|
||||
pub fn join(self) -> Result<Option<Editor>> {
|
||||
if let Some(handle) = self.0 {
|
||||
let editor = handle.join().unwrap()?;
|
||||
return Ok(Some(editor));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
55
src/editor/zellij.rs
Normal file
55
src/editor/zellij.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use std::process::Command;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::editor::run_cmd;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Pane {
|
||||
id: u32,
|
||||
}
|
||||
|
||||
pub fn parse_pane_id(b: &[u8]) -> Option<(String, u32)> {
|
||||
// Remove newline
|
||||
let b = b.get("terminal_".len()..b.len().saturating_sub(1))?;
|
||||
let id_str = str::from_utf8(b).ok()?;
|
||||
|
||||
let (first, rest) = b.split_first()?;
|
||||
let mut id = u32::from(first - b'0');
|
||||
|
||||
for c in rest {
|
||||
id = 10 * id + u32::from(c - b'0');
|
||||
}
|
||||
|
||||
Some((id_str.to_owned(), id))
|
||||
}
|
||||
|
||||
pub fn pane_open(pane_id: u32) -> Result<bool> {
|
||||
let mut stdout = run_cmd(
|
||||
Command::new("zellij")
|
||||
.arg("action")
|
||||
.arg("list-panes")
|
||||
.arg("-j"),
|
||||
)?;
|
||||
|
||||
// Remove newline
|
||||
stdout.pop();
|
||||
|
||||
let panes = serde_json::de::from_slice::<Vec<Pane>>(&stdout)
|
||||
.context("Failed to parse the output of `zellij action list-panes -j`")?;
|
||||
|
||||
Ok(panes.iter().any(|pane| pane.id == pane_id))
|
||||
}
|
||||
|
||||
pub fn close_pane(pane_id: &str) -> Result<()> {
|
||||
run_cmd(
|
||||
Command::new("zellij")
|
||||
.arg("action")
|
||||
.arg("close-pane")
|
||||
.arg("-p")
|
||||
.arg(pane_id),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -20,10 +20,10 @@ struct ExerciseFiles {
|
||||
}
|
||||
|
||||
fn create_dir_if_not_exists(path: &str) -> Result<()> {
|
||||
if let Err(e) = create_dir(path) {
|
||||
if e.kind() != io::ErrorKind::AlreadyExists {
|
||||
return Err(Error::from(e).context(format!("Failed to create the directory {path}")));
|
||||
}
|
||||
if let Err(e) = create_dir(path)
|
||||
&& e.kind() != io::ErrorKind::AlreadyExists
|
||||
{
|
||||
return Err(Error::from(e).context(format!("Failed to create the directory {path}")));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -85,7 +85,7 @@ impl EmbeddedFiles {
|
||||
exercise_path.truncate(prefix.len());
|
||||
exercise_path.push_str(dir.name);
|
||||
exercise_path.push('/');
|
||||
exercise_path.push_str(&exercise_info.name);
|
||||
exercise_path.push_str(exercise_info.name);
|
||||
exercise_path.push_str(".rs");
|
||||
|
||||
fs::write(&exercise_path, exercise_files.exercise)
|
||||
@@ -141,18 +141,19 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ExerciseInfo {
|
||||
dir: String,
|
||||
struct ExerciseInfo<'a> {
|
||||
dir: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct InfoFile {
|
||||
exercises: Vec<ExerciseInfo>,
|
||||
struct InfoFile<'a> {
|
||||
#[serde(borrow)]
|
||||
exercises: Vec<ExerciseInfo<'a>>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
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`")
|
||||
.exercises;
|
||||
|
||||
|
||||
@@ -1,28 +1,34 @@
|
||||
use anyhow::Result;
|
||||
use crossterm::{
|
||||
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
|
||||
QueueableCommand,
|
||||
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
|
||||
};
|
||||
use std::io::{self, StdoutLock, Write};
|
||||
|
||||
use crate::{
|
||||
cmd::CmdRunner,
|
||||
term::{self, terminal_file_link, write_ansi, CountedWrite},
|
||||
term::{self, CountedWrite, file_path, terminal_file_link, write_ansi},
|
||||
};
|
||||
|
||||
/// The initial capacity of the output buffer.
|
||||
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.write_all(b"Solution")?;
|
||||
stdout.queue(ResetColor)?;
|
||||
stdout.write_all(b" for comparison: ")?;
|
||||
if let Some(canonical_path) = term::canonicalize(solution_path) {
|
||||
terminal_file_link(stdout, solution_path, &canonical_path, Color::Cyan)?;
|
||||
} else {
|
||||
stdout.write_all(solution_path.as_bytes())?;
|
||||
}
|
||||
file_path(stdout, Color::Cyan, |writer| {
|
||||
if emit_file_links && let Some(canonical_path) = term::canonicalize(solution_path) {
|
||||
terminal_file_link(writer, solution_path, &canonical_path)
|
||||
} else {
|
||||
writer.stdout().write_all(solution_path.as_bytes())
|
||||
}
|
||||
})?;
|
||||
stdout.write_all(b"\n")
|
||||
}
|
||||
|
||||
@@ -42,17 +48,17 @@ fn run_bin(
|
||||
|
||||
let success = cmd_runner.run_debug_bin(bin_name, output.as_deref_mut())?;
|
||||
|
||||
if let Some(output) = output {
|
||||
if !success {
|
||||
// This output is important to show the user that something went wrong.
|
||||
// Otherwise, calling something like `exit(1)` in an exercise without further output
|
||||
// leaves the user confused about why the exercise isn't done yet.
|
||||
write_ansi(output, SetAttribute(Attribute::Bold));
|
||||
write_ansi(output, SetForegroundColor(Color::Red));
|
||||
output.extend_from_slice(b"The exercise didn't run successfully (nonzero exit code)");
|
||||
write_ansi(output, ResetColor);
|
||||
output.push(b'\n');
|
||||
}
|
||||
if let Some(output) = output
|
||||
&& !success
|
||||
{
|
||||
// This output is important to show the user that something went wrong.
|
||||
// Otherwise, calling something like `exit(1)` in an exercise without further output
|
||||
// leaves the user confused about why the exercise isn't done yet.
|
||||
write_ansi(output, SetAttribute(Attribute::Bold));
|
||||
write_ansi(output, SetForegroundColor(Color::Red));
|
||||
output.extend_from_slice(b"The exercise didn't run successfully (nonzero exit code)");
|
||||
write_ansi(output, ResetColor);
|
||||
output.push(b'\n');
|
||||
}
|
||||
|
||||
Ok(success)
|
||||
@@ -60,8 +66,8 @@ fn run_bin(
|
||||
|
||||
/// See `info_file::ExerciseInfo`
|
||||
pub struct Exercise {
|
||||
pub dir: Option<&'static str>,
|
||||
pub name: &'static str,
|
||||
pub dir: Option<&'static str>,
|
||||
/// Path of the exercise file starting with the `exercises/` directory.
|
||||
pub path: &'static str,
|
||||
pub canonical_path: Option<String>,
|
||||
@@ -72,12 +78,18 @@ pub struct Exercise {
|
||||
}
|
||||
|
||||
impl Exercise {
|
||||
pub fn terminal_file_link<'a>(&self, writer: &mut impl CountedWrite<'a>) -> io::Result<()> {
|
||||
if let Some(canonical_path) = self.canonical_path.as_deref() {
|
||||
return terminal_file_link(writer, self.path, canonical_path, Color::Blue);
|
||||
}
|
||||
|
||||
writer.write_str(self.path)
|
||||
pub fn terminal_file_link<'a>(
|
||||
&self,
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +158,6 @@ pub trait RunnableExercise {
|
||||
|
||||
/// Compile, check and run the exercise.
|
||||
/// The output is written to the `output` buffer after clearing it.
|
||||
#[inline]
|
||||
fn run_exercise(&self, output: Option<&mut Vec<u8>>, cmd_runner: &CmdRunner) -> Result<bool> {
|
||||
self.run::<false>(self.name(), output, cmd_runner)
|
||||
}
|
||||
@@ -189,22 +200,18 @@ pub trait RunnableExercise {
|
||||
}
|
||||
|
||||
impl RunnableExercise for Exercise {
|
||||
#[inline]
|
||||
fn name(&self) -> &str {
|
||||
self.name
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn dir(&self) -> Option<&str> {
|
||||
self.dir
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strict_clippy(&self) -> bool {
|
||||
self.strict_clippy
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn test(&self) -> bool {
|
||||
self.test
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use anyhow::{bail, Context, Error, Result};
|
||||
use anyhow::{Context, Error, Result, bail};
|
||||
use serde::Deserialize;
|
||||
use std::{fs, io::ErrorKind};
|
||||
|
||||
@@ -8,9 +8,9 @@ use crate::{embedded::EMBEDDED_FILES, exercise::RunnableExercise};
|
||||
#[derive(Deserialize)]
|
||||
pub struct ExerciseInfo {
|
||||
/// Exercise's unique name.
|
||||
pub name: String,
|
||||
pub name: &'static str,
|
||||
/// Exercise's directory name inside the `exercises/` directory.
|
||||
pub dir: Option<String>,
|
||||
pub dir: Option<&'static str>,
|
||||
/// Run `cargo test` on the exercise.
|
||||
#[serde(default = "default_true")]
|
||||
pub test: bool,
|
||||
@@ -18,12 +18,11 @@ pub struct ExerciseInfo {
|
||||
#[serde(default)]
|
||||
pub strict_clippy: bool,
|
||||
/// The exercise's hint to be shown to the user on request.
|
||||
pub hint: String,
|
||||
pub hint: &'static str,
|
||||
/// The exercise is already solved. Ignore it when checking that all exercises are unsolved.
|
||||
#[serde(default)]
|
||||
pub skip_check_unsolved: bool,
|
||||
}
|
||||
#[inline(always)]
|
||||
const fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
@@ -31,7 +30,7 @@ const fn default_true() -> bool {
|
||||
impl ExerciseInfo {
|
||||
/// Path to the exercise file starting with the `exercises/` directory.
|
||||
pub fn path(&self) -> String {
|
||||
let mut path = if let Some(dir) = &self.dir {
|
||||
let mut path = if let Some(dir) = self.dir {
|
||||
// 14 = 10 + 1 + 3
|
||||
// exercises/ + / + .rs
|
||||
let mut path = String::with_capacity(14 + dir.len() + self.name.len());
|
||||
@@ -47,7 +46,7 @@ impl ExerciseInfo {
|
||||
path
|
||||
};
|
||||
|
||||
path.push_str(&self.name);
|
||||
path.push_str(self.name);
|
||||
path.push_str(".rs");
|
||||
|
||||
path
|
||||
@@ -55,22 +54,18 @@ impl ExerciseInfo {
|
||||
}
|
||||
|
||||
impl RunnableExercise for ExerciseInfo {
|
||||
#[inline]
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
self.name
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn dir(&self) -> Option<&str> {
|
||||
self.dir.as_deref()
|
||||
self.dir
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strict_clippy(&self) -> bool {
|
||||
self.strict_clippy
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn test(&self) -> bool {
|
||||
self.test
|
||||
}
|
||||
@@ -79,27 +74,35 @@ impl RunnableExercise for ExerciseInfo {
|
||||
/// The deserialized `info.toml` file.
|
||||
#[derive(Deserialize)]
|
||||
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,
|
||||
/// Shown to users when starting with the exercises.
|
||||
pub welcome_message: Option<String>,
|
||||
pub welcome_message: Option<&'static str>,
|
||||
/// Shown to users after finishing all exercises.
|
||||
pub final_message: Option<String>,
|
||||
pub final_message: Option<&'static str>,
|
||||
/// List of all exercises.
|
||||
pub exercises: Vec<ExerciseInfo>,
|
||||
}
|
||||
|
||||
impl InfoFile {
|
||||
/// 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> {
|
||||
// Read a local `info.toml` if it exists.
|
||||
let slf = match fs::read_to_string("info.toml") {
|
||||
Ok(file_content) => toml_edit::de::from_str::<Self>(&file_content)
|
||||
.context("Failed to parse the `info.toml` file")?,
|
||||
let slf = match fs::read("info.toml") {
|
||||
Ok(file_content) => {
|
||||
// Remove `\r` on Windows.
|
||||
// Leaking is fine since the info file is used until the end of the program.
|
||||
let file_content =
|
||||
String::from_utf8(file_content.into_iter().filter(|c| *c != b'\r').collect())
|
||||
.context("Failed to parse `info.toml` as UTF8")?
|
||||
.leak();
|
||||
toml::de::from_str::<Self>(file_content)
|
||||
.context("Failed to parse the `info.toml` file")?
|
||||
}
|
||||
Err(e) => {
|
||||
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");
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user