mirror of
https://github.com/rust-lang/rustlings.git
synced 2026-05-15 09:48:45 +00:00
Merge branch 'main' into rustlingsua
This commit is contained in:
commit
d6caefb139
17
.github/workflows/rust.yml
vendored
17
.github/workflows/rust.yml
vendored
@ -5,11 +5,13 @@ on:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- website
|
||||
- .github/workflows/website.yml
|
||||
- '*.md'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- website
|
||||
- .github/workflows/website.yml
|
||||
- '*.md'
|
||||
|
||||
env:
|
||||
@ -19,13 +21,13 @@ jobs:
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Clippy
|
||||
run: cargo clippy -- --deny warnings
|
||||
fmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: rustfmt
|
||||
run: cargo fmt --all --check
|
||||
test:
|
||||
@ -34,14 +36,21 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: swatinem/rust-cache@v2
|
||||
- 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: rustlings dev check
|
||||
run: cargo dev check --require-solutions
|
||||
rumdl:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rvben/rumdl@v0
|
||||
|
||||
19
.github/workflows/website.yml
vendored
19
.github/workflows/website.yml
vendored
@ -4,29 +4,36 @@ on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
paths: [website]
|
||||
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@v4
|
||||
- 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.20.0/zola-v0.20.0-x86_64-unknown-linux-gnu.tar.gz | tar xz
|
||||
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@v3
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
path: website/public/
|
||||
|
||||
deploy:
|
||||
needs: build
|
||||
# Grant GITHUB_TOKEN the permissions required to make a Pages deployment
|
||||
@ -40,4 +47,4 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: actions/deploy-pages@v4
|
||||
uses: actions/deploy-pages@v5
|
||||
|
||||
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"]
|
||||
211
CHANGELOG.md
211
CHANGELOG.md
@ -1,5 +1,26 @@
|
||||
# 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.
|
||||
|
||||
## 6.5.0 (2025-08-21)
|
||||
|
||||
### Added
|
||||
@ -105,17 +126,17 @@
|
||||
|
||||
## 6.1.0 (2024-07-10)
|
||||
|
||||
#### Added
|
||||
### Added
|
||||
|
||||
- `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.
|
||||
@ -192,11 +213,11 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
|
||||
## 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.
|
||||
@ -207,7 +228,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
|
||||
## 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.
|
||||
@ -215,7 +236,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- `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` :)
|
||||
@ -223,7 +244,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- `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.
|
||||
@ -238,7 +259,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- `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!
|
||||
@ -246,13 +267,13 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
|
||||
## 5.5.1 (2023-05-17)
|
||||
|
||||
#### Fixed
|
||||
### Fixed
|
||||
|
||||
- Reverted `rust-project.json` path generation due to an upstream `rust-analyzer` fix.
|
||||
|
||||
## 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
|
||||
@ -260,7 +281,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- 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
|
||||
@ -269,7 +290,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- `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
|
||||
@ -279,20 +300,20 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- `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
|
||||
|
||||
## 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
|
||||
@ -301,7 +322,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
|
||||
## 5.4.0 (2023-02-12)
|
||||
|
||||
#### Changed
|
||||
### Changed
|
||||
|
||||
- Reordered exercises
|
||||
- Unwrapped `standard_library_types` into `iterators` and `smart_pointers`
|
||||
@ -313,7 +334,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- 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
|
||||
@ -323,23 +344,23 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- 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
|
||||
|
||||
## 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
|
||||
@ -353,7 +374,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- **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
|
||||
@ -361,28 +382,28 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
|
||||
## 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
|
||||
|
||||
## 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
|
||||
@ -390,18 +411,18 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
|
||||
## 5.1.1 (2022-08-17)
|
||||
|
||||
#### Bug Fixes
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed an incorrect assertion in options1
|
||||
|
||||
## 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
|
||||
@ -421,7 +442,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- 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
|
||||
@ -430,7 +451,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
|
||||
## 5.0.0 (2022-07-16)
|
||||
|
||||
#### Features
|
||||
### Features
|
||||
|
||||
- Hint comments in exercises now also include a reference to the
|
||||
`hint` watch mode subcommand.
|
||||
@ -462,7 +483,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- 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.
|
||||
@ -486,7 +507,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
`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`.
|
||||
@ -494,7 +515,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
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.
|
||||
@ -502,53 +523,53 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
|
||||
## 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.
|
||||
|
||||
## 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.
|
||||
|
||||
## 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:**
|
||||
@ -575,14 +596,14 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- **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))
|
||||
|
||||
## 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))
|
||||
@ -591,7 +612,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- **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))
|
||||
@ -604,12 +625,12 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
|
||||
## 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))
|
||||
@ -624,7 +645,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
|
||||
## 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))
|
||||
@ -652,7 +673,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- **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))
|
||||
@ -665,7 +686,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
|
||||
## 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))
|
||||
@ -673,7 +694,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- 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))
|
||||
@ -687,14 +708,14 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
|
||||
## 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))
|
||||
@ -707,7 +728,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
|
||||
## 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))
|
||||
@ -717,7 +738,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- **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))
|
||||
@ -729,12 +750,12 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
|
||||
## 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))
|
||||
@ -743,7 +764,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- 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))
|
||||
@ -770,11 +791,11 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
|
||||
## 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))
|
||||
@ -783,26 +804,26 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- 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))
|
||||
|
||||
### 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))
|
||||
|
||||
## 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:**
|
||||
@ -817,7 +838,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- 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))
|
||||
@ -829,7 +850,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
|
||||
## 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))
|
||||
@ -840,33 +861,33 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- **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))
|
||||
|
||||
## 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))
|
||||
|
||||
### 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))
|
||||
@ -877,7 +898,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
|
||||
## 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))
|
||||
@ -893,15 +914,15 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- 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))
|
||||
|
||||
### 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))
|
||||
@ -909,7 +930,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
|
||||
## 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))
|
||||
@ -918,18 +939,18 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- **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))
|
||||
|
||||
### 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)
|
||||
@ -938,29 +959,29 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- Fix broken link (#164, @HanKruiger)
|
||||
- Remove highlighting and syntect (#167, @komaeda)
|
||||
|
||||
### 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
|
||||
|
||||
### 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
|
||||
|
||||
### 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)
|
||||
|
||||
### 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)
|
||||
@ -970,7 +991,7 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- Fix links by deleting book version (@diodfr, #142)
|
||||
- Canonicalize paths to fix path matching (@cjpearce, #143)
|
||||
|
||||
### 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)
|
||||
@ -979,12 +1000,12 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
||||
- Give a warning when Rustlings isn't run from the right directory (#123)
|
||||
- Verify that rust version is recent enough to install Rustlings (#131)
|
||||
|
||||
### 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
|
||||
|
||||
### 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`
|
||||
|
||||
631
Cargo.lock
generated
631
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
15
Cargo.toml
15
Cargo.toml
@ -18,8 +18,8 @@ edition = "2024" # On Update: Update the edition of `rustfmt` in `dev check` and
|
||||
rust-version = "1.88"
|
||||
|
||||
[workspace.dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
toml = { version = "0.9", default-features = false, features = ["std", "parse", "serde"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
toml = { version = "1", default-features = false, features = ["std", "parse", "serde"] }
|
||||
|
||||
[package]
|
||||
name = "rustlings"
|
||||
@ -45,20 +45,21 @@ include = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
anyhow = "1"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
crossterm = { version = "0.29", default-features = false, features = ["windows", "events"] }
|
||||
notify = "8.0"
|
||||
notify = "8"
|
||||
rustlings-macros = { path = "rustlings-macros", version = "=6.5.0" }
|
||||
serde_json = "1.0"
|
||||
serde_json = "1"
|
||||
serde.workspace = true
|
||||
shlex = "1"
|
||||
toml.workspace = true
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
rustix = { version = "1.0", default-features = false, features = ["std", "stdio", "termios"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.21"
|
||||
tempfile = "3"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
|
||||
@ -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,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)
|
||||
|
||||
@ -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"));
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 |
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
set -e
|
||||
|
||||
typos
|
||||
cargo upgrades
|
||||
|
||||
# Similar to CI
|
||||
cargo clippy -- --deny warnings
|
||||
|
||||
@ -16,7 +16,7 @@ include = [
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
quote = "1.0"
|
||||
quote = "1"
|
||||
serde.workspace = true
|
||||
toml.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:
|
||||
@ -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"""
|
||||
@ -449,7 +439,7 @@ 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."""
|
||||
such as struct-like, tuple-like and unit-only variants."""
|
||||
|
||||
[[exercises]]
|
||||
name = "enums3"
|
||||
|
||||
@ -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::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;
|
||||
}
|
||||
|
||||
|
||||
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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");
|
||||
impl Fireworks {
|
||||
fn new() -> Self {
|
||||
Self { rockets: 0 }
|
||||
}
|
||||
|
||||
Self {
|
||||
sender_country,
|
||||
recipient_country,
|
||||
weight_in_grams,
|
||||
}
|
||||
fn add_rockets(&mut self, rockets: usize) {
|
||||
self.rockets += rockets
|
||||
}
|
||||
|
||||
fn is_international(&self) -> bool {
|
||||
// ^^^^^^^ added
|
||||
self.sender_country != self.recipient_country
|
||||
}
|
||||
|
||||
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), "🚀🚀🚀🚀🚀🚀🚀");
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ fn main() {
|
||||
// `.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.
|
||||
// But if it is called where `&str` is expected, then `&str` is kept as `&str` since no conversion is needed.
|
||||
// If you remove the `#[allow(…)]` line, then Clippy will tell you to remove `.into()` below since it is a useless conversion.
|
||||
#[allow(clippy::useless_conversion)]
|
||||
string_slice("nice weather".into());
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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`.
|
||||
|
||||
@ -4,7 +4,7 @@ fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
|
||||
|
||||
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;
|
||||
@ -21,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"));
|
||||
|
||||
@ -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,17 +10,18 @@ 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:?}");
|
||||
|
||||
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;
|
||||
|
||||
@ -2,7 +2,6 @@ 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::{MAIN_SEPARATOR_STR, Path},
|
||||
@ -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,21 +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,
|
||||
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()
|
||||
@ -83,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,43 +179,37 @@ impl AppState {
|
||||
official_exercises: !Path::new("info.toml").exists(),
|
||||
cmd_runner,
|
||||
// VS Code has its own file link handling
|
||||
emit_file_links: env::var_os("TERM_PROGRAM").is_none_or(|v| v != "vscode"),
|
||||
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 emit_file_links(&self) -> bool {
|
||||
self.emit_file_links
|
||||
}
|
||||
@ -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.
|
||||
@ -440,7 +430,7 @@ impl AppState {
|
||||
.is_err()
|
||||
{
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
let success = exercise.run_exercise(None, &slf.cmd_runner);
|
||||
let progress = match success {
|
||||
@ -567,6 +557,28 @@ 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";
|
||||
@ -599,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,
|
||||
@ -616,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(),
|
||||
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'/');
|
||||
}
|
||||
@ -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,
|
||||
},
|
||||
];
|
||||
|
||||
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),
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ mod new;
|
||||
mod update;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum DevCommands {
|
||||
pub enum DevCommand {
|
||||
/// Create a new project for community exercises
|
||||
New {
|
||||
/// The path to create the project in
|
||||
@ -26,7 +26,7 @@ pub enum DevCommands {
|
||||
Update,
|
||||
}
|
||||
|
||||
impl DevCommands {
|
||||
impl DevCommand {
|
||||
pub fn run(self) -> Result<()> {
|
||||
match self {
|
||||
Self::New { path, no_git } => {
|
||||
|
||||
@ -63,7 +63,7 @@ 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`");
|
||||
}
|
||||
@ -76,7 +76,7 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
|
||||
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`");
|
||||
}
|
||||
@ -214,7 +214,7 @@ 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<_>, _>>()
|
||||
|
||||
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(())
|
||||
}
|
||||
@ -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,13 +141,14 @@ 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]
|
||||
|
||||
@ -66,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>,
|
||||
@ -158,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)
|
||||
}
|
||||
@ -201,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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -82,9 +77,9 @@ pub struct InfoFile {
|
||||
/// 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>,
|
||||
}
|
||||
@ -94,9 +89,17 @@ impl InfoFile {
|
||||
/// 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::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::de::from_str(EMBEDDED_FILES.info_file)
|
||||
|
||||
30
src/init.rs
30
src/init.rs
@ -5,10 +5,10 @@ use crossterm::{
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
env::set_current_dir,
|
||||
env::{current_dir, set_current_dir},
|
||||
fs::{self, create_dir},
|
||||
io::{self, Write},
|
||||
path::{Path, PathBuf},
|
||||
path::Path,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
@ -18,8 +18,9 @@ use crate::{
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CargoLocateProject {
|
||||
root: PathBuf,
|
||||
struct CargoLocateProject<'a> {
|
||||
#[serde(borrow)]
|
||||
root: &'a Path,
|
||||
}
|
||||
|
||||
pub fn init() -> Result<()> {
|
||||
@ -72,9 +73,9 @@ pub fn init() -> Result<()> {
|
||||
)?
|
||||
.root;
|
||||
|
||||
let workspace_manifest_content = fs::read_to_string(&workspace_manifest)
|
||||
let workspace_manifest_content = fs::read_to_string(workspace_manifest)
|
||||
.with_context(|| format!("Failed to read the file {}", workspace_manifest.display()))?;
|
||||
if !workspace_manifest_content.contains("[workspace]\n")
|
||||
if !workspace_manifest_content.contains("[workspace]")
|
||||
&& !workspace_manifest_content.contains("workspace.")
|
||||
{
|
||||
bail!(
|
||||
@ -106,7 +107,7 @@ pub fn init() -> Result<()> {
|
||||
}
|
||||
|
||||
stdout.write_all(b"The directory `rustlings` has been added to `workspace.members` in the `Cargo.toml` file of this Cargo workspace.\n")?;
|
||||
fs::remove_dir_all("rustlings")
|
||||
fs::remove_dir_all(rustlings_dir)
|
||||
.context("Failed to remove the temporary directory `rustlings/`")?;
|
||||
init_git = false;
|
||||
} else {
|
||||
@ -168,7 +169,17 @@ pub fn init() -> Result<()> {
|
||||
fs::write(".vscode/extensions.json", VS_CODE_EXTENSIONS_JSON)
|
||||
.context("Failed to create the file `rustlings/.vscode/extensions.json`")?;
|
||||
|
||||
if init_git {
|
||||
if init_git && let Ok(dir) = current_dir() {
|
||||
let mut dir = dir.as_path();
|
||||
|
||||
loop {
|
||||
if dir.join(".git").exists() || dir.join(".jj").exists() {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Some(parent) = dir.parent() {
|
||||
dir = parent;
|
||||
} else {
|
||||
// Ignore any Git error because Git initialization is not required.
|
||||
let _ = Command::new("git")
|
||||
.arg("init")
|
||||
@ -176,6 +187,9 @@ pub fn init() -> Result<()> {
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stdout.queue(SetForegroundColor(Color::Green))?;
|
||||
|
||||
@ -11,9 +11,10 @@ use crossterm::{
|
||||
};
|
||||
use std::io::{self, StdoutLock, Write};
|
||||
|
||||
use crate::app_state::AppState;
|
||||
|
||||
use self::state::{Filter, ListState};
|
||||
use crate::{
|
||||
app_state::AppState,
|
||||
list::state::{Filter, ListState},
|
||||
};
|
||||
|
||||
mod scroll_state;
|
||||
mod state;
|
||||
@ -82,7 +83,7 @@ fn handle_list(app_state: &mut AppState, stdout: &mut StdoutLock) -> Result<()>
|
||||
}
|
||||
}
|
||||
KeyCode::Char('r') => list_state.reset_selected()?,
|
||||
KeyCode::Char('c') => {
|
||||
KeyCode::Char('c') | KeyCode::Enter => {
|
||||
if list_state.selected_to_current_exercise()? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@ -19,7 +19,6 @@ impl ScrollState {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn offset(&self) -> usize {
|
||||
self.offset
|
||||
}
|
||||
@ -41,7 +40,6 @@ impl ScrollState {
|
||||
.min(global_max_offset);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn selected(&self) -> Option<usize> {
|
||||
self.selected
|
||||
}
|
||||
@ -86,12 +84,10 @@ impl ScrollState {
|
||||
self.set_selected(self.selected.map_or(0, |selected| selected.min(n_rows - 1)));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn update_scroll_padding(&mut self) {
|
||||
self.scroll_padding = (self.max_n_rows_to_display / 4).min(self.max_scroll_padding);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn max_n_rows_to_display(&self) -> usize {
|
||||
self.max_n_rows_to_display
|
||||
}
|
||||
|
||||
@ -15,11 +15,10 @@ use std::{
|
||||
use crate::{
|
||||
app_state::AppState,
|
||||
exercise::Exercise,
|
||||
list::scroll_state::ScrollState,
|
||||
term::{CountedWrite, MaxLenWriter, progress_bar},
|
||||
};
|
||||
|
||||
use super::scroll_state::ScrollState;
|
||||
|
||||
const COL_SPACING: usize = 2;
|
||||
const SELECTED_ROW_ATTRIBUTES: Attributes = Attributes::none()
|
||||
.with(Attribute::Reverse)
|
||||
@ -229,7 +228,7 @@ impl<'a> ListState<'a> {
|
||||
progress_bar(
|
||||
&mut MaxLenWriter::new(stdout, self.term_width as usize),
|
||||
self.app_state.n_done(),
|
||||
self.app_state.exercises().len() as u16,
|
||||
self.app_state.exercises().len() as u32,
|
||||
self.term_width,
|
||||
)?;
|
||||
next_ln(stdout)?;
|
||||
@ -238,11 +237,24 @@ impl<'a> ListState<'a> {
|
||||
if self.message.is_empty() {
|
||||
// Help footer message
|
||||
if self.scroll_state.selected().is_some() {
|
||||
writer.write_str("↓/j ↑/k home/g end/G | <c>ontinue at | <r>eset exercise")?;
|
||||
writer.write_str("↓/")?;
|
||||
hotkey(&mut writer, b"j")?;
|
||||
writer.write_str(" ↑/")?;
|
||||
hotkey(&mut writer, b"k")?;
|
||||
writer.write_ascii(b" home/")?;
|
||||
hotkey(&mut writer, b"g")?;
|
||||
writer.write_ascii(b" end/")?;
|
||||
hotkey(&mut writer, b"G")?;
|
||||
writer.write_str(" | ↩️/")?;
|
||||
hotkey(&mut writer, b"c")?;
|
||||
writer.write_ascii(b"ontinue at | ")?;
|
||||
hotkey(&mut writer, b"r")?;
|
||||
writer.write_ascii(b"eset exercise")?;
|
||||
next_ln(stdout)?;
|
||||
writer = MaxLenWriter::new(stdout, self.term_width as usize);
|
||||
|
||||
writer.write_ascii(b"<s>earch | filter ")?;
|
||||
hotkey(&mut writer, b"s")?;
|
||||
writer.write_ascii(b"earch | filter ")?;
|
||||
} else {
|
||||
// Nothing selected (and nothing shown), so only display filter and quit.
|
||||
writer.write_ascii(b"filter ")?;
|
||||
@ -250,27 +262,41 @@ impl<'a> ListState<'a> {
|
||||
|
||||
match self.filter {
|
||||
Filter::Done => {
|
||||
writer.stdout.queue(SetAttribute(Attribute::Underlined))?;
|
||||
hotkey(&mut writer, b"d")?;
|
||||
writer
|
||||
.stdout
|
||||
.queue(SetForegroundColor(Color::Magenta))?
|
||||
.queue(SetAttribute(Attribute::Underlined))?;
|
||||
writer.write_ascii(b"<d>one")?;
|
||||
writer.write_str("one")?;
|
||||
writer.stdout.queue(ResetColor)?;
|
||||
writer.write_ascii(b"/<p>ending")?;
|
||||
writer.write_ascii(b"/")?;
|
||||
hotkey(&mut writer, b"p")?;
|
||||
writer.write_ascii(b"ending")?;
|
||||
}
|
||||
Filter::Pending => {
|
||||
writer.write_ascii(b"<d>one/")?;
|
||||
hotkey(&mut writer, b"d")?;
|
||||
writer.write_ascii(b"one/")?;
|
||||
writer.stdout.queue(SetAttribute(Attribute::Underlined))?;
|
||||
hotkey(&mut writer, b"p")?;
|
||||
writer
|
||||
.stdout
|
||||
.queue(SetForegroundColor(Color::Magenta))?
|
||||
.queue(SetAttribute(Attribute::Underlined))?;
|
||||
writer.write_ascii(b"<p>ending")?;
|
||||
writer.write_ascii(b"ending")?;
|
||||
writer.stdout.queue(ResetColor)?;
|
||||
}
|
||||
Filter::None => writer.write_ascii(b"<d>one/<p>ending")?,
|
||||
Filter::None => {
|
||||
hotkey(&mut writer, b"d")?;
|
||||
writer.write_ascii(b"one/")?;
|
||||
hotkey(&mut writer, b"p")?;
|
||||
writer.write_ascii(b"ending")?;
|
||||
}
|
||||
}
|
||||
|
||||
writer.write_ascii(b" | <q>uit list")?;
|
||||
writer.write_ascii(b" | ")?;
|
||||
hotkey(&mut writer, b"q")?;
|
||||
writer.write_ascii(b"uit list")?;
|
||||
} else {
|
||||
writer.stdout.queue(SetForegroundColor(Color::Magenta))?;
|
||||
writer.write_str(&self.message)?;
|
||||
@ -304,7 +330,6 @@ impl<'a> ListState<'a> {
|
||||
self.scroll_state.set_n_rows(n_rows);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn filter(&self) -> Filter {
|
||||
self.filter
|
||||
}
|
||||
@ -314,22 +339,18 @@ impl<'a> ListState<'a> {
|
||||
self.update_rows();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn select_next(&mut self) {
|
||||
self.scroll_state.select_next();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn select_previous(&mut self) {
|
||||
self.scroll_state.select_previous();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn select_first(&mut self) {
|
||||
self.scroll_state.select_first();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn select_last(&mut self) {
|
||||
self.scroll_state.select_last();
|
||||
}
|
||||
@ -366,11 +387,11 @@ impl<'a> ListState<'a> {
|
||||
|
||||
let exercise_ind = self.selected_to_exercise_ind(selected)?;
|
||||
let exercise_name = self.app_state.reset_exercise_by_ind(exercise_ind)?;
|
||||
self.update_rows();
|
||||
write!(
|
||||
self.message,
|
||||
"The exercise `{exercise_name}` has been reset",
|
||||
)?;
|
||||
self.update_rows();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -415,3 +436,14 @@ impl<'a> ListState<'a> {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw an emphasized hotkey in the list footer.
|
||||
fn hotkey(writer: &mut MaxLenWriter, hotkey: &[u8]) -> io::Result<()> {
|
||||
writer
|
||||
.stdout
|
||||
.queue(SetForegroundColor(Color::Yellow))?
|
||||
.queue(SetAttribute(Attribute::Bold))?;
|
||||
writer.write_ascii(hotkey)?;
|
||||
writer.stdout.queue(ResetColor)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
99
src/main.rs
99
src/main.rs
@ -1,19 +1,27 @@
|
||||
use anyhow::{Context, Result, bail};
|
||||
use app_state::StateFileStatus;
|
||||
use clap::{Parser, Subcommand};
|
||||
use clap::Parser;
|
||||
use std::{
|
||||
env,
|
||||
io::{self, IsTerminal, Write},
|
||||
path::Path,
|
||||
process::ExitCode,
|
||||
};
|
||||
use term::{clear_terminal, press_enter_prompt};
|
||||
|
||||
use self::{app_state::AppState, dev::DevCommands, info_file::InfoFile};
|
||||
use crate::{
|
||||
app_state::AppState,
|
||||
cli::{Args, Command},
|
||||
editor::Editor,
|
||||
info_file::InfoFile,
|
||||
};
|
||||
|
||||
mod app_state;
|
||||
mod cargo_toml;
|
||||
mod cli;
|
||||
mod cmd;
|
||||
mod dev;
|
||||
mod editor;
|
||||
mod embedded;
|
||||
mod exercise;
|
||||
mod info_file;
|
||||
@ -25,44 +33,6 @@ mod watch;
|
||||
|
||||
const CURRENT_FORMAT_VERSION: u8 = 1;
|
||||
|
||||
/// Rustlings is a collection of small exercises to get you used to writing and reading Rust code
|
||||
#[derive(Parser)]
|
||||
#[command(version)]
|
||||
struct Args {
|
||||
#[command(subcommand)]
|
||||
command: Option<Subcommands>,
|
||||
/// Manually run the current exercise using `r` in the watch mode.
|
||||
/// Only use this if Rustlings fails to detect exercise file changes.
|
||||
#[arg(long)]
|
||||
manual_run: bool,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Subcommands {
|
||||
/// 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(DevCommands),
|
||||
}
|
||||
|
||||
fn main() -> Result<ExitCode> {
|
||||
let args = Args::parse();
|
||||
|
||||
@ -72,8 +42,8 @@ fn main() -> Result<ExitCode> {
|
||||
|
||||
'priority_cmd: {
|
||||
match args.command {
|
||||
Some(Subcommands::Init) => init::init().context("Initialization failed")?,
|
||||
Some(Subcommands::Dev(dev_command)) => dev_command.run()?,
|
||||
Some(Command::Init) => init::init().context("Initialization failed")?,
|
||||
Some(Command::Dev(dev_command)) => dev_command.run()?,
|
||||
_ => break 'priority_cmd,
|
||||
}
|
||||
|
||||
@ -91,9 +61,18 @@ fn main() -> Result<ExitCode> {
|
||||
bail!(FORMAT_VERSION_HIGHER_ERR);
|
||||
}
|
||||
|
||||
let vs_code_term = env::var_os("TERM_PROGRAM").is_some_and(|v| v == "vscode");
|
||||
let editor = if args.no_editor {
|
||||
None
|
||||
} else {
|
||||
Editor::new(args.edit_cmd, vs_code_term)?
|
||||
};
|
||||
|
||||
let (mut app_state, state_file_status) = AppState::new(
|
||||
info_file.exercises,
|
||||
info_file.final_message.unwrap_or_default(),
|
||||
editor,
|
||||
vs_code_term,
|
||||
)?;
|
||||
|
||||
// Show the welcome message if the state file doesn't exist yet.
|
||||
@ -128,7 +107,7 @@ fn main() -> Result<ExitCode> {
|
||||
None
|
||||
} else {
|
||||
// For the notify event handler thread.
|
||||
// Leaking is not a problem because the slice lives until the end of the program.
|
||||
// Leaking is fine since the slice is used until the end of the program.
|
||||
Some(
|
||||
&*app_state
|
||||
.exercises()
|
||||
@ -140,14 +119,15 @@ fn main() -> Result<ExitCode> {
|
||||
};
|
||||
|
||||
watch::watch(&mut app_state, notify_exercise_names)?;
|
||||
app_state.close_editor()?;
|
||||
}
|
||||
Some(Subcommands::Run { name }) => {
|
||||
Some(Command::Run { name }) => {
|
||||
if let Some(name) = name {
|
||||
app_state.set_current_exercise_by_name(&name)?;
|
||||
}
|
||||
return run::run(&mut app_state);
|
||||
}
|
||||
Some(Subcommands::CheckAll) => {
|
||||
Some(Command::CheckAll) => {
|
||||
let mut stdout = io::stdout().lock();
|
||||
if let Some(first_pending_exercise_ind) = app_state.check_all_exercises(&mut stdout)? {
|
||||
if app_state.current_exercise().done {
|
||||
@ -171,23 +151,36 @@ fn main() -> Result<ExitCode> {
|
||||
stdout.write_all(b"\n")?;
|
||||
|
||||
return Ok(ExitCode::FAILURE);
|
||||
} else {
|
||||
}
|
||||
|
||||
app_state.render_final_message(&mut stdout)?;
|
||||
}
|
||||
}
|
||||
Some(Subcommands::Reset { name }) => {
|
||||
Some(Command::Reset { name }) => {
|
||||
app_state.set_current_exercise_by_name(&name)?;
|
||||
let exercise_path = app_state.reset_current_exercise()?;
|
||||
println!("The exercise {exercise_path} has been reset");
|
||||
app_state.reset_current_exercise()?;
|
||||
|
||||
let current_exercise = app_state.current_exercise();
|
||||
let mut stdout = io::stdout().lock();
|
||||
stdout.write_all(b"The exercise ")?;
|
||||
current_exercise.terminal_file_link(&mut stdout, app_state.emit_file_links())?;
|
||||
stdout.write_all(b" has been reset\n")?;
|
||||
}
|
||||
Some(Subcommands::Hint { name }) => {
|
||||
Some(Command::Hint { name }) => {
|
||||
if let Some(name) = name {
|
||||
app_state.set_current_exercise_by_name(&name)?;
|
||||
}
|
||||
println!("{}", app_state.current_exercise().hint);
|
||||
|
||||
let current_exercise = app_state.current_exercise();
|
||||
let mut stdout = io::stdout().lock();
|
||||
stdout.write_all(b"Current exercise: ")?;
|
||||
current_exercise.terminal_file_link(&mut stdout, app_state.emit_file_links())?;
|
||||
|
||||
stdout.write_all(b"\n\nHint:\n")?;
|
||||
stdout.write_all(current_exercise.hint.as_bytes())?;
|
||||
stdout.write_all(b"\n")?;
|
||||
}
|
||||
// Handled in an earlier match.
|
||||
Some(Subcommands::Init | Subcommands::Dev(_)) => (),
|
||||
Some(Command::Init | Command::Dev(_)) => (),
|
||||
}
|
||||
|
||||
Ok(ExitCode::SUCCESS)
|
||||
|
||||
24
src/term.rs
24
src/term.rs
@ -18,7 +18,6 @@ pub struct MaxLenWriter<'a, 'lock> {
|
||||
}
|
||||
|
||||
impl<'a, 'lock> MaxLenWriter<'a, 'lock> {
|
||||
#[inline]
|
||||
pub fn new(stdout: &'a mut StdoutLock<'lock>, max_len: usize) -> Self {
|
||||
Self {
|
||||
stdout,
|
||||
@ -28,7 +27,6 @@ impl<'a, 'lock> MaxLenWriter<'a, 'lock> {
|
||||
}
|
||||
|
||||
// Additional is for emojis that take more space.
|
||||
#[inline]
|
||||
pub fn add_to_len(&mut self, additional: usize) {
|
||||
self.len += additional;
|
||||
}
|
||||
@ -64,24 +62,20 @@ impl<'lock> CountedWrite<'lock> for MaxLenWriter<'_, 'lock> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn stdout(&mut self) -> &mut StdoutLock<'lock> {
|
||||
self.stdout
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> CountedWrite<'a> for StdoutLock<'a> {
|
||||
#[inline]
|
||||
fn write_ascii(&mut self, ascii: &[u8]) -> io::Result<()> {
|
||||
self.write_all(ascii)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn write_str(&mut self, unicode: &str) -> io::Result<()> {
|
||||
self.write_all(unicode.as_bytes())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn stdout(&mut self) -> &mut StdoutLock<'a> {
|
||||
self
|
||||
}
|
||||
@ -193,19 +187,19 @@ impl Drop for ProgressCounter<'_, '_> {
|
||||
|
||||
pub fn progress_bar<'a>(
|
||||
writer: &mut impl CountedWrite<'a>,
|
||||
progress: u16,
|
||||
total: u16,
|
||||
progress: u32,
|
||||
total: u32,
|
||||
term_width: u16,
|
||||
) -> io::Result<()> {
|
||||
debug_assert!(total <= 999);
|
||||
debug_assert!(progress <= total);
|
||||
|
||||
const PREFIX: &[u8] = b"Progress: [";
|
||||
const PREFIX_WIDTH: u16 = PREFIX.len() as u16;
|
||||
const POSTFIX_WIDTH: u16 = "] xxx/xxx".len() as u16;
|
||||
const WRAPPER_WIDTH: u16 = PREFIX_WIDTH + POSTFIX_WIDTH;
|
||||
const MIN_LINE_WIDTH: u16 = WRAPPER_WIDTH + 4;
|
||||
|
||||
debug_assert!(total <= 999);
|
||||
debug_assert!(progress <= total);
|
||||
|
||||
if term_width < MIN_LINE_WIDTH {
|
||||
writer.write_ascii(b"Progress: ")?;
|
||||
// Integers are in ASCII.
|
||||
@ -215,7 +209,8 @@ pub fn progress_bar<'a>(
|
||||
let stdout = writer.stdout();
|
||||
stdout.write_all(PREFIX)?;
|
||||
|
||||
let width = term_width - WRAPPER_WIDTH;
|
||||
// Use u32 to prevent the intermediate multiplication from overflowing
|
||||
let width = u32::from(term_width - WRAPPER_WIDTH);
|
||||
let filled = (width * progress) / total;
|
||||
|
||||
stdout.queue(SetForegroundColor(Color::Green))?;
|
||||
@ -225,16 +220,15 @@ pub fn progress_bar<'a>(
|
||||
|
||||
if filled < width {
|
||||
stdout.write_all(b">")?;
|
||||
}
|
||||
|
||||
let width_minus_filled = width - filled;
|
||||
if width_minus_filled > 1 {
|
||||
let red_part_width = width_minus_filled - 1;
|
||||
stdout.queue(SetForegroundColor(Color::Red))?;
|
||||
for _ in 0..red_part_width {
|
||||
for _ in 1..width_minus_filled {
|
||||
stdout.write_all(b"-")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stdout.queue(SetForegroundColor(Color::Reset))?;
|
||||
|
||||
|
||||
@ -13,10 +13,9 @@ use std::{
|
||||
use crate::{
|
||||
app_state::{AppState, ExercisesProgress},
|
||||
list,
|
||||
watch::{notify_event::NotifyEventHandler, state::WatchState, terminal_event::InputEvent},
|
||||
};
|
||||
|
||||
use self::{notify_event::NotifyEventHandler, state::WatchState, terminal_event::InputEvent};
|
||||
|
||||
mod notify_event;
|
||||
mod state;
|
||||
mod terminal_event;
|
||||
@ -28,7 +27,6 @@ static EXERCISE_RUNNING: AtomicBool = AtomicBool::new(false);
|
||||
pub struct InputPauseGuard(());
|
||||
|
||||
impl InputPauseGuard {
|
||||
#[inline]
|
||||
pub fn scoped_pause() -> Self {
|
||||
EXERCISE_RUNNING.store(true, Relaxed);
|
||||
Self(())
|
||||
@ -36,7 +34,6 @@ impl InputPauseGuard {
|
||||
}
|
||||
|
||||
impl Drop for InputPauseGuard {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
EXERCISE_RUNNING.store(false, Relaxed);
|
||||
}
|
||||
@ -175,8 +172,7 @@ pub fn watch(
|
||||
watch_list_loop(app_state, notify_exercise_names)
|
||||
}
|
||||
|
||||
const QUIT_MSG: &[u8] = b"
|
||||
|
||||
const QUIT_MSG: &[u8] = b"q\n
|
||||
We hope you're enjoying learning Rust!
|
||||
If you want to continue working on the exercises at a later point, you can simply run `rustlings` again in this directory.
|
||||
";
|
||||
|
||||
@ -12,7 +12,7 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use super::{EXERCISE_RUNNING, WatchEvent};
|
||||
use crate::watch::{EXERCISE_RUNNING, WatchEvent};
|
||||
|
||||
const DEBOUNCE_DURATION: Duration = Duration::from_millis(200);
|
||||
|
||||
|
||||
@ -17,10 +17,9 @@ use crate::{
|
||||
clear_terminal,
|
||||
exercise::{OUTPUT_CAPACITY, RunnableExercise, solution_link_line},
|
||||
term::progress_bar,
|
||||
watch::{InputPauseGuard, WatchEvent, terminal_event::terminal_event_handler},
|
||||
};
|
||||
|
||||
use super::{InputPauseGuard, WatchEvent, terminal_event::terminal_event_handler};
|
||||
|
||||
const HEADING_ATTRIBUTES: Attributes = Attributes::none()
|
||||
.with(Attribute::Bold)
|
||||
.with(Attribute::Underlined);
|
||||
@ -60,7 +59,7 @@ impl<'a> WatchState<'a> {
|
||||
watch_event_sender,
|
||||
terminal_event_unpause_receiver,
|
||||
manual_run,
|
||||
)
|
||||
);
|
||||
})
|
||||
.context("Failed to spawn a thread to handle terminal events")?;
|
||||
|
||||
@ -79,14 +78,16 @@ impl<'a> WatchState<'a> {
|
||||
// Ignore any input until running the exercise is done.
|
||||
let _input_pause_guard = InputPauseGuard::scoped_pause();
|
||||
|
||||
self.show_hint = false;
|
||||
|
||||
writeln!(
|
||||
stdout,
|
||||
"\nChecking the exercise `{}`. Please wait…",
|
||||
self.app_state.current_exercise().name,
|
||||
)?;
|
||||
|
||||
let editor_handle = self.app_state.open_editor()?;
|
||||
|
||||
self.show_hint = false;
|
||||
|
||||
let success = self
|
||||
.app_state
|
||||
.current_exercise()
|
||||
@ -106,7 +107,9 @@ impl<'a> WatchState<'a> {
|
||||
self.done_status = DoneStatus::Pending;
|
||||
}
|
||||
|
||||
self.app_state.join_editor_handle(editor_handle)?;
|
||||
self.render(stdout)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -128,9 +131,10 @@ impl<'a> WatchState<'a> {
|
||||
|
||||
match answer[0] {
|
||||
b'y' | b'Y' => {
|
||||
self.app_state.close_editor()?;
|
||||
self.app_state.reset_current_exercise()?;
|
||||
|
||||
// The file watcher reruns the exercise otherwise.
|
||||
// The file watcher reruns the exercise otherwise
|
||||
if self.manual_run {
|
||||
self.run_current_exercise(stdout)?;
|
||||
}
|
||||
@ -245,7 +249,7 @@ impl<'a> WatchState<'a> {
|
||||
progress_bar(
|
||||
stdout,
|
||||
self.app_state.n_done(),
|
||||
self.app_state.exercises().len() as u16,
|
||||
self.app_state.exercises().len() as u32,
|
||||
self.term_width,
|
||||
)?;
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ use std::sync::{
|
||||
mpsc::{Receiver, Sender},
|
||||
};
|
||||
|
||||
use super::{EXERCISE_RUNNING, WatchEvent};
|
||||
use crate::watch::{EXERCISE_RUNNING, WatchEvent};
|
||||
|
||||
pub enum InputEvent {
|
||||
Next,
|
||||
@ -47,7 +47,7 @@ pub fn terminal_event_handler(
|
||||
// Pause input until quitting the confirmation prompt.
|
||||
if unpause_receiver.recv().is_err() {
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
@ -64,7 +64,7 @@ pub fn terminal_event_handler(
|
||||
return;
|
||||
}
|
||||
}
|
||||
Ok(Event::FocusGained | Event::FocusLost | Event::Mouse(_)) => continue,
|
||||
Ok(Event::FocusGained | Event::FocusLost | Event::Mouse(_)) => (),
|
||||
Err(e) => break WatchEvent::TerminalEventErr(e),
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
use std::{
|
||||
env::{self, consts::EXE_SUFFIX},
|
||||
process::{Command, Stdio},
|
||||
str::from_utf8,
|
||||
};
|
||||
|
||||
enum Output<'a> {
|
||||
FullStdout(&'a str),
|
||||
PartialStdout(&'a str),
|
||||
PartialStderr(&'a str),
|
||||
}
|
||||
@ -20,39 +18,24 @@ struct Cmd<'a> {
|
||||
}
|
||||
|
||||
impl<'a> Cmd<'a> {
|
||||
#[inline]
|
||||
fn current_dir(&mut self, current_dir: &'a str) -> &mut Self {
|
||||
self.current_dir = Some(current_dir);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn args(&mut self, args: &'a [&'a str]) -> &mut Self {
|
||||
self.args = args;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn output(&mut self, output: Output<'a>) -> &mut Self {
|
||||
self.output = Some(output);
|
||||
self
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert(&self, success: bool) {
|
||||
let rustlings_bin = {
|
||||
let mut path = env::current_exe().unwrap();
|
||||
// Pop test binary name
|
||||
path.pop();
|
||||
// Pop `/deps`
|
||||
path.pop();
|
||||
|
||||
path.push("rustlings");
|
||||
let mut path = path.into_os_string();
|
||||
path.push(EXE_SUFFIX);
|
||||
path
|
||||
};
|
||||
|
||||
let mut cmd = Command::new(rustlings_bin);
|
||||
let mut cmd = Command::new(env!("CARGO_BIN_EXE_rustlings"));
|
||||
|
||||
if let Some(current_dir) = self.current_dir {
|
||||
cmd.current_dir(current_dir);
|
||||
@ -60,38 +43,32 @@ impl<'a> Cmd<'a> {
|
||||
|
||||
cmd.args(self.args).stdin(Stdio::null());
|
||||
|
||||
let status = match self.output {
|
||||
None => cmd
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status()
|
||||
.unwrap(),
|
||||
Some(FullStdout(stdout)) => {
|
||||
let output = cmd.stderr(Stdio::null()).output().unwrap();
|
||||
assert_eq!(from_utf8(&output.stdout).unwrap(), stdout);
|
||||
output.status
|
||||
}
|
||||
let output = cmd.output().unwrap();
|
||||
match self.output {
|
||||
None => (),
|
||||
Some(PartialStdout(stdout)) => {
|
||||
let output = cmd.stderr(Stdio::null()).output().unwrap();
|
||||
assert!(from_utf8(&output.stdout).unwrap().contains(stdout));
|
||||
output.status
|
||||
}
|
||||
Some(PartialStderr(stderr)) => {
|
||||
let output = cmd.stdout(Stdio::null()).output().unwrap();
|
||||
assert!(from_utf8(&output.stderr).unwrap().contains(stderr));
|
||||
output.status
|
||||
}
|
||||
};
|
||||
|
||||
assert_eq!(status.success(), success, "{cmd:?}");
|
||||
if output.status.success() != success {
|
||||
panic!(
|
||||
"{cmd:?}\n\nstdout:\n{}\n\nstderr:\n{}",
|
||||
from_utf8(&output.stdout).unwrap(),
|
||||
from_utf8(&output.stderr).unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
fn success(&self) {
|
||||
self.assert(true);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
fn fail(&self) {
|
||||
self.assert(false);
|
||||
}
|
||||
@ -148,7 +125,7 @@ fn hint() {
|
||||
Cmd::default()
|
||||
.current_dir("tests/test_exercises")
|
||||
.args(&["hint", "test_failure"])
|
||||
.output(FullStdout("The answer to everything: 42\n"))
|
||||
.output(PartialStdout("\n\nHint:\nThe answer to everything: 42\n"))
|
||||
.success();
|
||||
}
|
||||
|
||||
|
||||
@ -6,11 +6,11 @@ compile_sass = false
|
||||
build_search_index = false
|
||||
|
||||
[markdown]
|
||||
highlight_code = true
|
||||
highlight_theme = "dracula"
|
||||
|
||||
insert_anchor_links = "heading"
|
||||
|
||||
[markdown.highlighting]
|
||||
theme = "dracula"
|
||||
|
||||
[extra]
|
||||
logo_path = "images/happy_ferris.svg"
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ title = "Community Exercises"
|
||||
- 🇯🇵 [Japanese Rustlings](https://github.com/sotanengel/rustlings-jp):A Japanese translation of the Rustlings exercises.
|
||||
- 🇨🇳 [Simplified Chinese Rustlings](https://github.com/SandmeyerX/rustlings-zh-cn): A simplified Chinese translation of the Rustlings exercises.
|
||||
- 🇺🇦 [Rustlings in Ukrainian](https://github.com/noroutine/rustlings-ua): Translation of the Rustlings exercises in Ukrainian.
|
||||
- 🇰🇷 [Korean Rustlings](https://github.com/eoncheole/rustlings-kr): A Korean translation of the Rustlings exercises.
|
||||
|
||||
> You can use the same `rustlings` program that you installed with `cargo install rustlings` to run community exercises.
|
||||
|
||||
|
||||
@ -73,6 +73,10 @@ While working with Rustlings, please use a modern terminal for the best user exp
|
||||
The default terminal on Linux and Mac should be sufficient.
|
||||
On Windows, we recommend the [Windows Terminal](https://aka.ms/terminal).
|
||||
|
||||
### Offline documentation
|
||||
|
||||
Whenever you're working on Rustlings offline, you can access a local copy of the standard library documentation by running `rustup doc --std`.
|
||||
|
||||
## Usage
|
||||
|
||||
After being done with the setup, visit the [**usage**](@/usage/index.md) page for some info about using Rustlings 🚀
|
||||
|
||||
@ -41,14 +41,10 @@
|
||||
@apply md:w-3/4 lg:w-3/5;
|
||||
}
|
||||
blockquote {
|
||||
@apply px-3 pt-2 pb-0.5 mb-4 mt-2 border-s-4 border-white/80 bg-white/7 rounded-sm;
|
||||
@apply px-3 pt-2 pb-px mb-4 mt-2 border-s-4 border-white/80 bg-white/7 rounded-sm;
|
||||
}
|
||||
|
||||
pre {
|
||||
@apply px-2 pt-2 pb-px overflow-x-auto text-sm sm:text-base rounded-sm mt-2 mb-4 after:content-[attr(data-lang)] after:text-[8px] after:opacity-40 selection:bg-white/15;
|
||||
}
|
||||
pre code mark {
|
||||
@apply pb-0.5 pt-1 pr-px text-inherit rounded-xs;
|
||||
@apply px-2 pt-2 pb-1.5 overflow-x-auto text-sm sm:text-base rounded-sm mt-2 mb-4 selection:bg-white/15;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@tailwindcss/cli": "^4.1"
|
||||
"@tailwindcss/cli": "^4"
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user