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]
|
branches: [main]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- website
|
- website
|
||||||
|
- .github/workflows/website.yml
|
||||||
- '*.md'
|
- '*.md'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- website
|
- website
|
||||||
|
- .github/workflows/website.yml
|
||||||
- '*.md'
|
- '*.md'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@ -19,13 +21,13 @@ jobs:
|
|||||||
clippy:
|
clippy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
run: cargo clippy -- --deny warnings
|
run: cargo clippy -- --deny warnings
|
||||||
fmt:
|
fmt:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- name: rustfmt
|
- name: rustfmt
|
||||||
run: cargo fmt --all --check
|
run: cargo fmt --all --check
|
||||||
test:
|
test:
|
||||||
@ -34,14 +36,21 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: swatinem/rust-cache@v2
|
- uses: swatinem/rust-cache@v2
|
||||||
- name: cargo test
|
- name: cargo test
|
||||||
|
env:
|
||||||
|
RUST_BACKTRACE: 1
|
||||||
run: cargo test --workspace
|
run: cargo test --workspace
|
||||||
dev-check:
|
dev-check:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: swatinem/rust-cache@v2
|
- uses: swatinem/rust-cache@v2
|
||||||
- name: rustlings dev check
|
- name: rustlings dev check
|
||||||
run: cargo dev check --require-solutions
|
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:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
paths: [website]
|
paths:
|
||||||
|
- website
|
||||||
|
- .github/workflows/website.yml
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
rumdl:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: rvben/rumdl@v0
|
||||||
build:
|
build:
|
||||||
|
needs: rumdl
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: website
|
working-directory: website
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- name: Install TailwindCSS
|
- name: Install TailwindCSS
|
||||||
run: npm install
|
run: npm install
|
||||||
- name: Build CSS
|
- name: Build CSS
|
||||||
run: npx @tailwindcss/cli -m -i input.css -o static/main.css
|
run: npx @tailwindcss/cli -m -i input.css -o static/main.css
|
||||||
- name: Download Zola
|
- 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
|
- name: Build site
|
||||||
run: ./zola build
|
run: ./zola build
|
||||||
- name: Upload static files as artifact
|
- name: Upload static files as artifact
|
||||||
uses: actions/upload-pages-artifact@v3
|
uses: actions/upload-pages-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: website/public/
|
path: website/public/
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
needs: build
|
needs: build
|
||||||
# Grant GITHUB_TOKEN the permissions required to make a Pages deployment
|
# Grant GITHUB_TOKEN the permissions required to make a Pages deployment
|
||||||
@ -40,4 +47,4 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Deploy to GitHub Pages
|
- 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
|
## 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)
|
## 6.5.0 (2025-08-21)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@ -105,17 +126,17 @@
|
|||||||
|
|
||||||
## 6.1.0 (2024-07-10)
|
## 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 (including community ones) include at least one `TODO` comment.
|
||||||
- `dev check`: Check that all exercises actually fail to run (not already solved).
|
- `dev check`: Check that all exercises actually fail to run (not already solved).
|
||||||
|
|
||||||
#### Changed
|
### Changed
|
||||||
|
|
||||||
- Make enum variants more consistent between enum exercises.
|
- Make enum variants more consistent between enum exercises.
|
||||||
- `iterators3`: Teach about the possible case of integer overflow during division.
|
- `iterators3`: Teach about the possible case of integer overflow during division.
|
||||||
|
|
||||||
#### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Exit with a helpful error message on missing/unsupported terminal/TTY.
|
- Exit with a helpful error message on missing/unsupported terminal/TTY.
|
||||||
- Mark the last exercise as done.
|
- Mark the last exercise as done.
|
||||||
@ -192,11 +213,11 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
|||||||
|
|
||||||
## 5.6.1 (2023-09-18)
|
## 5.6.1 (2023-09-18)
|
||||||
|
|
||||||
#### Changed
|
### Changed
|
||||||
|
|
||||||
- Converted all exercises with assertions to test mode.
|
- Converted all exercises with assertions to test mode.
|
||||||
|
|
||||||
#### Fixed
|
### Fixed
|
||||||
|
|
||||||
- `cow1`: Reverted regression introduced by calling `to_mut` where it
|
- `cow1`: Reverted regression introduced by calling `to_mut` where it
|
||||||
shouldn't have been called, and clarified comment.
|
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)
|
## 5.6.0 (2023-09-04)
|
||||||
|
|
||||||
#### Added
|
### Added
|
||||||
|
|
||||||
- New exercise: `if3`, teaching the user about `if let` statements.
|
- 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.
|
- `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.
|
- `if1`: Added a test case to check equal values.
|
||||||
- `if3`: Added a note specifying that there are no test changes needed.
|
- `if3`: Added a note specifying that there are no test changes needed.
|
||||||
|
|
||||||
#### Changed
|
### Changed
|
||||||
|
|
||||||
- Swapped the order of threads and smart pointer exercises.
|
- Swapped the order of threads and smart pointer exercises.
|
||||||
- Rewrote the CLI to use `clap` - it's matured much since we switched to `argh` :)
|
- 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
|
- `move_semantics`: Switched 1-4 to tests, and rewrote them to be way simpler, while still teaching about the same
|
||||||
concepts.
|
concepts.
|
||||||
|
|
||||||
#### Fixed
|
### Fixed
|
||||||
|
|
||||||
- `iterators5`:
|
- `iterators5`:
|
||||||
- Removed an outdated part of the hint.
|
- 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.
|
- `cow1`: Added `.to_mut()` to distinguish from the previous test case.
|
||||||
- `threads2`: Updated hint text to reference the correct book heading.
|
- `threads2`: Updated hint text to reference the correct book heading.
|
||||||
|
|
||||||
#### Housekeeping
|
### Housekeeping
|
||||||
|
|
||||||
- Cleaned up the explanation paragraphs at the start of each exercise.
|
- Cleaned up the explanation paragraphs at the start of each exercise.
|
||||||
- Lots of Nix housekeeping that I don't feel qualified to write about!
|
- Lots of Nix housekeeping that I don't feel qualified to write about!
|
||||||
@ -246,13 +267,13 @@ Then follow the link to the guide about [community exercises](https://rustlings.
|
|||||||
|
|
||||||
## 5.5.1 (2023-05-17)
|
## 5.5.1 (2023-05-17)
|
||||||
|
|
||||||
#### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Reverted `rust-project.json` path generation due to an upstream `rust-analyzer` fix.
|
- Reverted `rust-project.json` path generation due to an upstream `rust-analyzer` fix.
|
||||||
|
|
||||||
## 5.5.0 (2023-05-17)
|
## 5.5.0 (2023-05-17)
|
||||||
|
|
||||||
#### Added
|
### Added
|
||||||
|
|
||||||
- `strings2`: Added a reference to the book chapter for reference conversion
|
- `strings2`: Added a reference to the book chapter for reference conversion
|
||||||
- `lifetimes`: Added a link to the lifetimekata project
|
- `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 `!` prefix command to watch mode that runs an external command
|
||||||
- Added a `--success-hints` option to watch mode that shows hints on exercise success
|
- Added a `--success-hints` option to watch mode that shows hints on exercise success
|
||||||
|
|
||||||
#### Changed
|
### Changed
|
||||||
|
|
||||||
- `vecs2`: Renamed iterator variable bindings for clarify
|
- `vecs2`: Renamed iterator variable bindings for clarify
|
||||||
- `lifetimes`: Changed order of book references
|
- `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
|
- `options2`: Improved tests for layering options
|
||||||
- `modules2`: Added more information to the hint
|
- `modules2`: Added more information to the hint
|
||||||
|
|
||||||
#### Fixed
|
### Fixed
|
||||||
|
|
||||||
- `errors2`: Corrected a comment wording
|
- `errors2`: Corrected a comment wording
|
||||||
- `iterators2`: Fixed a spelling mistake in the hint text
|
- `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
|
- `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
|
- `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
|
- Added a markdown linter to run on GitHub actions
|
||||||
- Split quick installation section into two code blocks
|
- Split quick installation section into two code blocks
|
||||||
|
|
||||||
## 5.4.1 (2023-03-10)
|
## 5.4.1 (2023-03-10)
|
||||||
|
|
||||||
#### Changed
|
### Changed
|
||||||
|
|
||||||
- `vecs`: Added links to `iter_mut` and `map` to README.md
|
- `vecs`: Added links to `iter_mut` and `map` to README.md
|
||||||
- `cow1`: Changed main to tests
|
- `cow1`: Changed main to tests
|
||||||
- `iterators1`: Formatted according to rustfmt
|
- `iterators1`: Formatted according to rustfmt
|
||||||
|
|
||||||
#### Fixed
|
### Fixed
|
||||||
|
|
||||||
- `errors5`: Unified undisclosed type notation
|
- `errors5`: Unified undisclosed type notation
|
||||||
- `arc1`: Improved readability by avoiding implicit dereference
|
- `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)
|
## 5.4.0 (2023-02-12)
|
||||||
|
|
||||||
#### Changed
|
### Changed
|
||||||
|
|
||||||
- Reordered exercises
|
- Reordered exercises
|
||||||
- Unwrapped `standard_library_types` into `iterators` and `smart_pointers`
|
- 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
|
- Made progress bar update proportional to amount of files verified
|
||||||
- Decreased `watch` delay from 2 to 1 second
|
- Decreased `watch` delay from 2 to 1 second
|
||||||
|
|
||||||
#### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Capitalized "Rust" in exercise hints
|
- Capitalized "Rust" in exercise hints
|
||||||
- **enums3**: Removed superfluous tuple brackets
|
- **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
|
- Fixed a typo in a method name
|
||||||
- Specified the edition in `rustc` commands
|
- Specified the edition in `rustc` commands
|
||||||
|
|
||||||
#### Housekeeping
|
### Housekeeping
|
||||||
|
|
||||||
- Bumped min Rust version to 1.58 in installation script
|
- Bumped min Rust version to 1.58 in installation script
|
||||||
|
|
||||||
## 5.3.0 (2022-12-23)
|
## 5.3.0 (2022-12-23)
|
||||||
|
|
||||||
#### Added
|
### Added
|
||||||
|
|
||||||
- **cli**: Added a percentage display in watch mode
|
- **cli**: Added a percentage display in watch mode
|
||||||
- Added a `flake.nix` for Nix users
|
- Added a `flake.nix` for Nix users
|
||||||
|
|
||||||
#### Changed
|
### Changed
|
||||||
|
|
||||||
- **structs3**: Added an additional test
|
- **structs3**: Added an additional test
|
||||||
- **macros**: Added a link to MacroKata in the README
|
- **macros**: Added a link to MacroKata in the README
|
||||||
|
|
||||||
#### Fixed
|
### Fixed
|
||||||
|
|
||||||
- **strings3**: Added a link to `std` in the hint
|
- **strings3**: Added a link to `std` in the hint
|
||||||
- **threads1**: Corrected a hint link
|
- **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
|
- **enums2**: Removed unnecessary indirection of self
|
||||||
- **enums3**: Added an extra tuple comment
|
- **enums3**: Added an extra tuple comment
|
||||||
|
|
||||||
#### Housekeeping
|
### Housekeeping
|
||||||
|
|
||||||
- Added a VSCode extension recommendation
|
- Added a VSCode extension recommendation
|
||||||
- Applied some Clippy and rustfmt formatting
|
- 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)
|
## 5.2.1 (2022-09-06)
|
||||||
|
|
||||||
#### Fixed
|
### Fixed
|
||||||
|
|
||||||
- **quiz1**: Reworded the comment to actually reflect what's going on in the tests.
|
- **quiz1**: Reworded the comment to actually reflect what's going on in the tests.
|
||||||
Also added another assert just to make sure.
|
Also added another assert just to make sure.
|
||||||
- **rc1**: Fixed a typo in the hint.
|
- **rc1**: Fixed a typo in the hint.
|
||||||
- **lifetimes**: Add quotes to the `println!` output, for readability.
|
- **lifetimes**: Add quotes to the `println!` output, for readability.
|
||||||
|
|
||||||
#### Housekeeping
|
### Housekeeping
|
||||||
|
|
||||||
- Fixed a typo in README.md
|
- Fixed a typo in README.md
|
||||||
|
|
||||||
## 5.2.0 (2022-08-27)
|
## 5.2.0 (2022-08-27)
|
||||||
|
|
||||||
#### Added
|
### Added
|
||||||
|
|
||||||
- Added a `reset` command
|
- Added a `reset` command
|
||||||
|
|
||||||
#### Changed
|
### Changed
|
||||||
|
|
||||||
- **options2**: Convert the exercise to use tests
|
- **options2**: Convert the exercise to use tests
|
||||||
|
|
||||||
#### Fixed
|
### Fixed
|
||||||
|
|
||||||
- **threads3**: Fixed a typo
|
- **threads3**: Fixed a typo
|
||||||
- **quiz1**: Adjusted the explanations to be consistent with
|
- **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)
|
## 5.1.1 (2022-08-17)
|
||||||
|
|
||||||
#### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
- Fixed an incorrect assertion in options1
|
- Fixed an incorrect assertion in options1
|
||||||
|
|
||||||
## 5.1.0 (2022-08-16)
|
## 5.1.0 (2022-08-16)
|
||||||
|
|
||||||
#### Features
|
### Features
|
||||||
|
|
||||||
- Added a new `rc1` exercise.
|
- Added a new `rc1` exercise.
|
||||||
- Added a new `cow1` exercise.
|
- Added a new `cow1` exercise.
|
||||||
|
|
||||||
#### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
- **variables5**: Corrected reference to previous exercise
|
- **variables5**: Corrected reference to previous exercise
|
||||||
- **functions4**: Fixed line number reference
|
- **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
|
- Added more granular tests
|
||||||
- Fixed some comment syntax shenanigans in info.toml
|
- Fixed some comment syntax shenanigans in info.toml
|
||||||
|
|
||||||
#### Housekeeping
|
### Housekeeping
|
||||||
|
|
||||||
- Fixed a typo in .editorconfig
|
- Fixed a typo in .editorconfig
|
||||||
- Fixed a typo in integration_tests.rs
|
- 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)
|
## 5.0.0 (2022-07-16)
|
||||||
|
|
||||||
#### Features
|
### Features
|
||||||
|
|
||||||
- Hint comments in exercises now also include a reference to the
|
- Hint comments in exercises now also include a reference to the
|
||||||
`hint` watch mode subcommand.
|
`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 lifetimes exercises.
|
||||||
- Added 3 new traits exercises.
|
- Added 3 new traits exercises.
|
||||||
|
|
||||||
#### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
- **variables2**: Made output messages more verbose.
|
- **variables2**: Made output messages more verbose.
|
||||||
- **variables5**: Added a nudging hint about shadowing.
|
- **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>`.
|
`Box<dyn Error>`.
|
||||||
- **try_from_into**: Fixed the function name in comment.
|
- **try_from_into**: Fixed the function name in comment.
|
||||||
|
|
||||||
#### Removed
|
### Removed
|
||||||
|
|
||||||
- Removed the legacy LSP feature that was using `mod.rs` files.
|
- Removed the legacy LSP feature that was using `mod.rs` files.
|
||||||
- Removed `quiz4`.
|
- 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
|
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.
|
simple, book-following style we've had in Rustlings.
|
||||||
|
|
||||||
#### Housekeeping
|
### Housekeeping
|
||||||
|
|
||||||
- Added missing exercises to the book index.
|
- Added missing exercises to the book index.
|
||||||
- Updated spacing in Cargo.toml.
|
- 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)
|
## 4.8.0 (2022-07-01)
|
||||||
|
|
||||||
#### Features
|
### Features
|
||||||
|
|
||||||
- Added a progress indicator for `rustlings watch`.
|
- Added a progress indicator for `rustlings watch`.
|
||||||
- The installation script now checks for Rustup being installed.
|
- The installation script now checks for Rustup being installed.
|
||||||
- Added a `rustlings lsp` command to enable `rust-analyzer`.
|
- Added a `rustlings lsp` command to enable `rust-analyzer`.
|
||||||
|
|
||||||
#### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
- **move_semantics5**: Replaced "in vogue" with "in scope" in hint.
|
- **move_semantics5**: Replaced "in vogue" with "in scope" in hint.
|
||||||
- **if2**: Fixed a typo in the hint.
|
- **if2**: Fixed a typo in the hint.
|
||||||
- **variables1**: Fixed an incorrect line reference in the hint.
|
- **variables1**: Fixed an incorrect line reference in the hint.
|
||||||
- Fixed an out of bounds check in the installation Bash script.
|
- 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.
|
- Replaced the git.io URL with the fully qualified URL because of git.io's sunsetting.
|
||||||
- Removed the deprecated Rust GitPod extension.
|
- Removed the deprecated Rust GitPod extension.
|
||||||
|
|
||||||
## 4.7.1 (2022-04-20)
|
## 4.7.1 (2022-04-20)
|
||||||
|
|
||||||
#### Features
|
### Features
|
||||||
|
|
||||||
- The amount of dependency crates that need to be compiled went down from ~65 to
|
- The amount of dependency crates that need to be compiled went down from ~65 to
|
||||||
~45 by bumping dependency versions.
|
~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 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)
|
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).
|
- **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
|
- **using_as**: A small part has been refactored to use `sum` instead of `fold`, resulting
|
||||||
in better readability.
|
in better readability.
|
||||||
|
|
||||||
#### Housekeeping
|
### Housekeeping
|
||||||
|
|
||||||
- The changelog will now be manually written instead of being automatically generated by the
|
- The changelog will now be manually written instead of being automatically generated by the
|
||||||
Git log.
|
Git log.
|
||||||
|
|
||||||
## 4.7.0 (2022-04-14)
|
## 4.7.0 (2022-04-14)
|
||||||
|
|
||||||
#### Features
|
### Features
|
||||||
|
|
||||||
- Add move_semantics6.rs exercise (#908) ([3f0e1303](https://github.com/rust-lang/rustlings/commit/3f0e1303e0b3bf3fecc0baced3c8b8a37f83c184))
|
- 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))
|
- **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))
|
- 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))
|
- Fix a few spelling mistakes ([1c0fe3cb](https://github.com/rust-lang/rustlings/commit/1c0fe3cbcca85f90b3985985b8e265ee872a2ab2))
|
||||||
- **cli:**
|
- **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))
|
- **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))
|
- **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))
|
- Add hints on how to get GCC installed (#741) ([bc56861](https://github.com/rust-lang/rustlings/commit/bc5686174463ad6f4f6b824b0e9b97c3039d4886))
|
||||||
- Fix some code blocks that were not highlighted ([17f9d74](https://github.com/rust-lang/rustlings/commit/17f9d7429ccd133a72e815fb5618e0ce79560929))
|
- Fix some code blocks that were not highlighted ([17f9d74](https://github.com/rust-lang/rustlings/commit/17f9d7429ccd133a72e815fb5618e0ce79560929))
|
||||||
|
|
||||||
## 4.6.0 (2021-09-25)
|
## 4.6.0 (2021-09-25)
|
||||||
|
|
||||||
#### Features
|
### Features
|
||||||
|
|
||||||
- add advanced_errs2 ([abd6b70c](https://github.com/rust-lang/rustlings/commit/abd6b70c72dc6426752ff41f09160b839e5c449e))
|
- add advanced_errs2 ([abd6b70c](https://github.com/rust-lang/rustlings/commit/abd6b70c72dc6426752ff41f09160b839e5c449e))
|
||||||
- add advanced_errs1 ([882d535b](https://github.com/rust-lang/rustlings/commit/882d535ba8628d5e0b37e8664b3e2f26260b2671))
|
- 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))
|
- **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))
|
- **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))
|
- 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))
|
- **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)
|
## 4.5.0 (2021-07-07)
|
||||||
|
|
||||||
#### Features
|
### Features
|
||||||
|
|
||||||
- Add move_semantics5 exercise. (#746) ([399ab328](https://github.com/rust-lang/rustlings/commit/399ab328d8d407265c09563aa4ef4534b2503ff2))
|
- 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))
|
- **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))
|
- 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))
|
- 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)
|
## 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))
|
- 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))
|
- 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))
|
- **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))
|
- **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 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))
|
- 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)
|
## 4.3.0 (2020-12-29)
|
||||||
|
|
||||||
#### Features
|
### Features
|
||||||
|
|
||||||
- Rewrite default out text ([44d39112](https://github.com/rust-lang/rustlings/commit/44d39112ff122b29c9793fe52e605df1612c6490))
|
- 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))
|
- 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))
|
- 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))
|
- **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))
|
- 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))
|
- 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)
|
## 4.2.0 (2020-11-07)
|
||||||
|
|
||||||
#### Features
|
### Features
|
||||||
|
|
||||||
- Add HashMap exercises ([633c00cf](https://github.com/rust-lang/rustlings/commit/633c00cf8071e1e82959a3010452a32f34f29fc9))
|
- Add HashMap exercises ([633c00cf](https://github.com/rust-lang/rustlings/commit/633c00cf8071e1e82959a3010452a32f34f29fc9))
|
||||||
- Add Vec exercises ([0c12fa31](https://github.com/rust-lang/rustlings/commit/0c12fa31c57c03c6287458a0a8aca7afd057baf6))
|
- 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))
|
- **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))
|
- **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))
|
- 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))
|
- 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)
|
## 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))
|
- 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))
|
- **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))
|
- **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))
|
- **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))
|
- 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))
|
- 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)
|
## 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))
|
- 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))
|
- 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 traits README ([173bb141](https://github.com/rust-lang/rustlings/commit/173bb14140c5530cbdb59e53ace3991a99d804af))
|
||||||
- Add box1.rs exercise ([7479a473](https://github.com/rust-lang/rustlings/commit/7479a4737bdcac347322ad0883ca528c8675e720))
|
- 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))
|
- 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))
|
- 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))
|
- 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))
|
- 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)
|
## 3.0.0 (2020-04-11)
|
||||||
|
|
||||||
#### Breaking Changes
|
### Breaking Changes
|
||||||
|
|
||||||
- make "compile" exercises print output (#278) ([3b6d5c](https://github.com/fmoko/rustlings/commit/3b6d5c3aaa27a242a832799eb66e96897d26fde3))
|
- 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))
|
- **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))
|
- **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))
|
- 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))
|
- 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 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 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))
|
- add new exercises for generics (#280) ([76be5e4e](https://github.com/rust-lang/rustlings/commit/76be5e4e991160f5fd9093f03ee2ba260e8f7229))
|
||||||
- **ci:** add buildkite config ([b049fa2c](https://github.com/rust-lang/rustlings/commit/b049fa2c84dba0f0c8906ac44e28fd45fba51a71))
|
- **ci:** add buildkite config ([b049fa2c](https://github.com/rust-lang/rustlings/commit/b049fa2c84dba0f0c8906ac44e28fd45fba51a71))
|
||||||
|
|
||||||
### 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))
|
- 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))
|
- Add clippy lints (#269) ([1e2fd9c9](https://github.com/rust-lang/rustlings/commit/1e2fd9c92f8cd6e389525ca1a999fca4c90b5921))
|
||||||
|
|
||||||
## 2.2.0 (2020-02-25)
|
## 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))
|
- Update deps to version compatible with aarch64-pc-windows (#263) ([19a93428](https://github.com/rust-lang/rustlings/commit/19a93428b3c73d994292671f829bdc8e5b7b3401))
|
||||||
- **docs:**
|
- **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)
|
- 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))
|
- 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))
|
- 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))
|
- 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)
|
## 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))
|
- 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))
|
- **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))
|
- **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))
|
- **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))
|
- **watch:** show hint while watching ([8143d57b](https://github.com/rust-lang/rustlings/commit/8143d57b4e88c51341dd4a18a14c536042cc009c))
|
||||||
|
|
||||||
## 2.0.0 (2019-11-12)
|
## 2.0.0 (2019-11-12)
|
||||||
|
|
||||||
#### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
- **default:** Clarify the installation procedure ([c371b853](https://github.com/rust-lang/rustlings/commit/c371b853afa08947ddeebec0edd074b171eeaae0))
|
- **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))
|
- **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))
|
- **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))
|
- 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))
|
- 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))
|
- 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))
|
- **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))
|
- **cli:** check for rustc before doing anything ([36a033b8](https://github.com/rust-lang/rustlings/commit/36a033b87a6549c1e5639c908bf7381c84f4f425))
|
||||||
- **hint:** Add test for hint ([ce9fa6eb](https://github.com/rust-lang/rustlings/commit/ce9fa6ebbfdc3e7585d488d9409797285708316f))
|
- **hint:** Add test for hint ([ce9fa6eb](https://github.com/rust-lang/rustlings/commit/ce9fa6ebbfdc3e7585d488d9409797285708316f))
|
||||||
|
|
||||||
### 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))
|
- **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))
|
- **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)
|
## 1.5.0 (2019-11-09)
|
||||||
|
|
||||||
#### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
- **test1:** Rewrite logic ([79a56942](https://github.com/rust-lang/rustlings/commit/79a569422c8309cfc9e4aed25bf4ab3b3859996b))
|
- **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))
|
- **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))
|
- 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))
|
- 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))
|
- 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))
|
- Added exercise for struct update syntax ([1c4c8764](https://github.com/rust-lang/rustlings/commit/1c4c8764ed118740cd4cee73272ddc6cceb9d959))
|
||||||
- **iterators2:** adds iterators2 exercise including config ([9288fccf](https://github.com/rust-lang/rustlings/commit/9288fccf07a2c5043b76d0fd6491e4cf72d76031))
|
- **iterators2:** adds iterators2 exercise including config ([9288fccf](https://github.com/rust-lang/rustlings/commit/9288fccf07a2c5043b76d0fd6491e4cf72d76031))
|
||||||
|
|
||||||
### 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))
|
- **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))
|
- **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)
|
## 1.4.0 (2019-07-13)
|
||||||
|
|
||||||
#### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
- **installation:** Fix rustlings installation check ([7a252c47](https://github.com/rust-lang/rustlings/commit/7a252c475551486efb52f949b8af55803b700bc6))
|
- **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))
|
- **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))
|
- **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))
|
- **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))
|
- **changelog:** Use clog for changelogs ([34e31232](https://github.com/rust-lang/rustlings/commit/34e31232dfddde284a341c9609b33cd27d9d5724))
|
||||||
- **iterators2:** adds iterators2 exercise including config ([9288fccf](https://github.com/rust-lang/rustlings/commit/9288fccf07a2c5043b76d0fd6491e4cf72d76031))
|
- **iterators2:** adds iterators2 exercise including config ([9288fccf](https://github.com/rust-lang/rustlings/commit/9288fccf07a2c5043b76d0fd6491e4cf72d76031))
|
||||||
|
|
||||||
### 1.3.0 (2019-06-05)
|
## 1.3.0 (2019-06-05)
|
||||||
|
|
||||||
#### Features
|
### Features
|
||||||
|
|
||||||
- Adds a simple exercise for structures (#163, @briankung)
|
- 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)
|
- 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)
|
- 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)
|
- Fix broken link (#164, @HanKruiger)
|
||||||
- Remove highlighting and syntect (#167, @komaeda)
|
- 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
|
- 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)
|
- Fix the `--nocapture` feature (@komaeda)
|
||||||
- Provide a nicer error message for when you're in the wrong directory
|
- Provide a nicer error message for when you're in the wrong directory
|
||||||
|
|
||||||
### 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)
|
- Add errors to exercises that compile without user changes (@yvan-sraka)
|
||||||
- Use --nocapture when testing, enabling `println!` when running (@komaeda)
|
- Use --nocapture when testing, enabling `println!` when running (@komaeda)
|
||||||
|
|
||||||
### 1.1.1 (2019-04-14)
|
## 1.1.1 (2019-04-14)
|
||||||
|
|
||||||
#### Bug fixes
|
### Bug fixes
|
||||||
|
|
||||||
- Fix permissions on exercise files (@zacanger, #133)
|
- Fix permissions on exercise files (@zacanger, #133)
|
||||||
- Make installation checks more thorough (@komaeda, 1b3469f236bc6979c27f6e1a04e4138a88e55de3)
|
- 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)
|
- Fix links by deleting book version (@diodfr, #142)
|
||||||
- Canonicalize paths to fix path matching (@cjpearce, #143)
|
- 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)
|
- errors2.rs: update link to Rust book (#124)
|
||||||
- Start verification at most recently modified file (#120)
|
- 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)
|
- Give a warning when Rustlings isn't run from the right directory (#123)
|
||||||
- Verify that rust version is recent enough to install Rustlings (#131)
|
- Verify that rust version is recent enough to install Rustlings (#131)
|
||||||
|
|
||||||
### 1.0.1 (2019-03-06)
|
## 1.0.1 (2019-03-06)
|
||||||
|
|
||||||
- Adds a way to install Rustlings in one command (`curl -L https://git.io/rustlings | bash`)
|
- Adds a way to install Rustlings in one command (`curl -L https://git.io/rustlings | bash`)
|
||||||
- Makes `rustlings watch` react to create file events (@shaunbennett, #117)
|
- Makes `rustlings watch` react to create file events (@shaunbennett, #117)
|
||||||
- Reworks the exercise management to use an external TOML file instead of just listing them in the code
|
- Reworks the exercise management to use an external TOML file instead of just listing them in the code
|
||||||
|
|
||||||
### 1.0.0 (2019-03-06)
|
## 1.0.0 (2019-03-06)
|
||||||
|
|
||||||
Initial release.
|
Initial release.
|
||||||
|
|||||||
@ -14,7 +14,7 @@ I want to …
|
|||||||
|
|
||||||
## Issues
|
## 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:
|
If you're reporting a bug, please include the output of the following commands:
|
||||||
|
|
||||||
- `cargo --version`
|
- `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"
|
rust-version = "1.88"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
toml = { version = "0.9", default-features = false, features = ["std", "parse", "serde"] }
|
toml = { version = "1", default-features = false, features = ["std", "parse", "serde"] }
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "rustlings"
|
name = "rustlings"
|
||||||
@ -45,20 +45,21 @@ include = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1"
|
||||||
clap = { version = "4.5", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
crossterm = { version = "0.29", default-features = false, features = ["windows", "events"] }
|
crossterm = { version = "0.29", default-features = false, features = ["windows", "events"] }
|
||||||
notify = "8.0"
|
notify = "8"
|
||||||
rustlings-macros = { path = "rustlings-macros", version = "=6.5.0" }
|
rustlings-macros = { path = "rustlings-macros", version = "=6.5.0" }
|
||||||
serde_json = "1.0"
|
serde_json = "1"
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
shlex = "1"
|
||||||
toml.workspace = true
|
toml.workspace = true
|
||||||
|
|
||||||
[target.'cfg(not(windows))'.dependencies]
|
[target.'cfg(not(windows))'.dependencies]
|
||||||
rustix = { version = "1.0", default-features = false, features = ["std", "stdio", "termios"] }
|
rustix = { version = "1.0", default-features = false, features = ["std", "stdio", "termios"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.21"
|
tempfile = "3"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
|||||||
@ -9,26 +9,6 @@ fn vec_loop(input: &[i32]) -> Vec<i32> {
|
|||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vec_map_example(input: &[i32]) -> Vec<i32> {
|
|
||||||
// An example of collecting a vector after mapping.
|
|
||||||
// We map each element of the `input` slice to its value plus 1.
|
|
||||||
// If the input is `[1, 2, 3]`, the output is `[2, 3, 4]`.
|
|
||||||
input.iter().map(|element| element + 1).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn vec_map(input: &[i32]) -> Vec<i32> {
|
|
||||||
// TODO: Here, we also want to multiply each element in the `input` slice
|
|
||||||
// by 2, but with iterator mapping instead of manually pushing into an empty
|
|
||||||
// vector.
|
|
||||||
// See the example in the function `vec_map_example` above.
|
|
||||||
input
|
|
||||||
.iter()
|
|
||||||
.map(|element| {
|
|
||||||
// ???
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// You can optionally experiment here.
|
// You can optionally experiment here.
|
||||||
}
|
}
|
||||||
@ -43,18 +23,4 @@ mod tests {
|
|||||||
let ans = vec_loop(&input);
|
let ans = vec_loop(&input);
|
||||||
assert_eq!(ans, [4, 8, 12, 16, 20]);
|
assert_eq!(ans, [4, 8, 12, 16, 20]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_vec_map_example() {
|
|
||||||
let input = [1, 2, 3];
|
|
||||||
let ans = vec_map_example(&input);
|
|
||||||
assert_eq!(ans, [2, 3, 4]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_vec_map() {
|
|
||||||
let input = [2, 4, 6, 8, 10];
|
|
||||||
let ans = vec_map(&input);
|
|
||||||
assert_eq!(ans, [4, 8, 12, 16, 20]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,38 +1,28 @@
|
|||||||
// Structs contain data, but can also have logic. In this exercise, we have
|
// 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)]
|
#[derive(Debug)]
|
||||||
struct Package {
|
struct Fireworks {
|
||||||
sender_country: String,
|
rockets: usize,
|
||||||
recipient_country: String,
|
|
||||||
weight_in_grams: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Package {
|
// TODO: Turn this function into an associated function on `Fireworks`.
|
||||||
fn new(sender_country: String, recipient_country: String, weight_in_grams: u32) -> Self {
|
fn new_fireworks() -> Fireworks {
|
||||||
if weight_in_grams < 10 {
|
Fireworks { rockets: 0 }
|
||||||
// This isn't how you should handle errors in Rust, but we will
|
}
|
||||||
// learn about error handling later.
|
|
||||||
panic!("Can't ship a package with weight below 10 grams");
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
// TODO: Turn this function into a method on `Fireworks`.
|
||||||
sender_country,
|
fn add_rockets(fireworks: &mut Fireworks, rockets: usize) {
|
||||||
recipient_country,
|
fireworks.rockets += rockets
|
||||||
weight_in_grams,
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Add the correct return type to the function signature.
|
// TODO: Turn this function into a method on `Fireworks`.
|
||||||
fn is_international(&self) {
|
fn start(fireworks: Fireworks) -> String {
|
||||||
// TODO: Read the tests that use this method to find out when a package
|
"🚀".repeat(fireworks.rockets)
|
||||||
// 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.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -44,44 +34,18 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
fn start_some_fireworks() {
|
||||||
fn fail_creating_weightless_package() {
|
let f = Fireworks::new();
|
||||||
let sender_country = String::from("Spain");
|
assert_eq!(f.start(), "");
|
||||||
let recipient_country = String::from("Austria");
|
|
||||||
|
|
||||||
Package::new(sender_country, recipient_country, 5);
|
let mut f = Fireworks::new();
|
||||||
}
|
f.add_rockets(3);
|
||||||
|
assert_eq!(f.start(), "🚀🚀🚀");
|
||||||
|
|
||||||
#[test]
|
let mut f = Fireworks::new();
|
||||||
fn create_international_package() {
|
f.add_rockets(7);
|
||||||
let sender_country = String::from("Spain");
|
// We don't use method syntax in the last test to ensure the `start`
|
||||||
let recipient_country = String::from("Russia");
|
// function takes ownership of the fireworks.
|
||||||
|
assert_eq!(Fireworks::start(f), "🚀🚀🚀🚀🚀🚀🚀");
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
# Strings
|
# 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
|
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.
|
to identify and create them, as well as use them.
|
||||||
|
|
||||||
## Further information
|
## 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 {
|
mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn iterators() {
|
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!();
|
let mut fav_fruits_iterator = todo!();
|
||||||
|
|
||||||
assert_eq!(fav_fruits_iterator.next(), Some(&"banana"));
|
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.
|
// TODO: Fix all the Clippy lints.
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
#[allow(unused_variables, unused_assignments)]
|
#[allow(unused_variables, unused_assignments)]
|
||||||
fn main() {
|
fn main() {
|
||||||
let my_option: Option<&str> = None;
|
let my_option: Option<&str> = None;
|
||||||
@ -11,14 +10,16 @@ fn main() {
|
|||||||
println!("{}", my_option.unwrap());
|
println!("{}", my_option.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
let my_arr = &[
|
let my_arr = &[
|
||||||
-1, -2, -3
|
-1, -2, -3
|
||||||
-4, -5, -6
|
-4, -5, -6
|
||||||
];
|
];
|
||||||
println!("My array! Here it is: {my_arr:?}");
|
println!("My array! Here it is: {my_arr:?}");
|
||||||
|
|
||||||
let my_empty_vec = vec![1, 2, 3, 4, 5].resize(0, 5);
|
let mut my_vec = vec![1, 2, 3, 4, 5];
|
||||||
println!("This Vec is empty, see? {my_empty_vec:?}");
|
my_vec.resize(0, 5);
|
||||||
|
println!("This Vec is empty, see? {my_vec:?}");
|
||||||
|
|
||||||
let mut value_a = 45;
|
let mut value_a = 45;
|
||||||
let mut value_b = 66;
|
let mut value_b = 66;
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
| vecs | §8.1 |
|
| vecs | §8.1 |
|
||||||
| move_semantics | §4.1-2 |
|
| move_semantics | §4.1-2 |
|
||||||
| structs | §5.1, §5.3 |
|
| structs | §5.1, §5.3 |
|
||||||
| enums | §6, §18.3 |
|
| enums | §6, §19.3 |
|
||||||
| strings | §8.2 |
|
| strings | §8.2 |
|
||||||
| modules | §7 |
|
| modules | §7 |
|
||||||
| hashmaps | §8.3 |
|
| hashmaps | §8.3 |
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
typos
|
typos
|
||||||
cargo upgrades
|
|
||||||
|
|
||||||
# Similar to CI
|
# Similar to CI
|
||||||
cargo clippy -- --deny warnings
|
cargo clippy -- --deny warnings
|
||||||
|
|||||||
@ -16,7 +16,7 @@ include = [
|
|||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
quote = "1.0"
|
quote = "1"
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
toml.workspace = true
|
toml.workspace = true
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,7 @@ get started, here are some notes about how Rustlings operates:
|
|||||||
|
|
||||||
final_message = """
|
final_message = """
|
||||||
We hope you enjoyed learning about the various aspects of Rust!
|
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!
|
You can also contribute your own exercises to help the greater community!
|
||||||
|
|
||||||
Before reporting an issue or contributing, please read our guidelines:
|
Before reporting an issue or contributing, please read our guidelines:
|
||||||
@ -318,16 +318,7 @@ of the Rust book to learn more."""
|
|||||||
name = "vecs2"
|
name = "vecs2"
|
||||||
dir = "05_vecs"
|
dir = "05_vecs"
|
||||||
hint = """
|
hint = """
|
||||||
In the first function, we create an empty vector and want to push new elements
|
Use the `.push()` method on the vector to push new elements to it."""
|
||||||
to it.
|
|
||||||
|
|
||||||
In the second function, we map the values of the input and collect them into
|
|
||||||
a vector.
|
|
||||||
|
|
||||||
After you've completed both functions, decide for yourself which approach you
|
|
||||||
like better.
|
|
||||||
|
|
||||||
What do you think is the more commonly used pattern under Rust developers?"""
|
|
||||||
|
|
||||||
# MOVE SEMANTICS
|
# MOVE SEMANTICS
|
||||||
|
|
||||||
@ -426,11 +417,10 @@ https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-
|
|||||||
name = "structs3"
|
name = "structs3"
|
||||||
dir = "07_structs"
|
dir = "07_structs"
|
||||||
hint = """
|
hint = """
|
||||||
For `is_international`: What makes a package international? Seems related to
|
Methods and associated functions are both declared in an `impl MyType {}`
|
||||||
the places it goes through right?
|
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
|
||||||
For `get_fees`: This method takes an additional argument, is there a field in
|
a `self` parameter.
|
||||||
the `Package` struct that this relates to?
|
|
||||||
|
|
||||||
Have a look in The Book to find out more about method implementations:
|
Have a look in The Book to find out more about method implementations:
|
||||||
https://doc.rust-lang.org/book/ch05-03-method-syntax.html"""
|
https://doc.rust-lang.org/book/ch05-03-method-syntax.html"""
|
||||||
@ -449,7 +439,7 @@ dir = "08_enums"
|
|||||||
test = false
|
test = false
|
||||||
hint = """
|
hint = """
|
||||||
You can create enumerations that have different variants with different types
|
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]]
|
[[exercises]]
|
||||||
name = "enums3"
|
name = "enums3"
|
||||||
|
|||||||
@ -3,20 +3,29 @@ use quote::quote;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct ExerciseInfo {
|
struct ExerciseInfo<'a> {
|
||||||
name: String,
|
name: &'a str,
|
||||||
dir: String,
|
dir: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct InfoFile {
|
struct InfoFile<'a> {
|
||||||
exercises: Vec<ExerciseInfo>,
|
#[serde(borrow)]
|
||||||
|
exercises: Vec<ExerciseInfo<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn include_files(_: TokenStream) -> TokenStream {
|
pub fn include_files(_: TokenStream) -> TokenStream {
|
||||||
let info_file = include_str!("../info.toml");
|
// Remove `\r` on Windows
|
||||||
let exercises = toml::de::from_str::<InfoFile>(info_file)
|
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`")
|
.expect("Failed to parse `info.toml`")
|
||||||
.exercises;
|
.exercises;
|
||||||
|
|
||||||
@ -37,7 +46,7 @@ pub fn include_files(_: TokenStream) -> TokenStream {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
dirs.push(exercise.dir.as_str());
|
dirs.push(exercise.dir);
|
||||||
*dir_ind = dirs.len() - 1;
|
*dir_ind = dirs.len() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,22 +8,6 @@ fn vec_loop(input: &[i32]) -> Vec<i32> {
|
|||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vec_map_example(input: &[i32]) -> Vec<i32> {
|
|
||||||
// An example of collecting a vector after mapping.
|
|
||||||
// We map each element of the `input` slice to its value plus 1.
|
|
||||||
// If the input is `[1, 2, 3]`, the output is `[2, 3, 4]`.
|
|
||||||
input.iter().map(|element| element + 1).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn vec_map(input: &[i32]) -> Vec<i32> {
|
|
||||||
// We will dive deeper into iterators, but for now, this is all what you
|
|
||||||
// had to do!
|
|
||||||
// Advanced note: This method is more efficient because it automatically
|
|
||||||
// preallocates enough capacity. This can be done manually in `vec_loop`
|
|
||||||
// using `Vec::with_capacity(input.len())` instead of `Vec::new()`.
|
|
||||||
input.iter().map(|element| 2 * element).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// You can optionally experiment here.
|
// You can optionally experiment here.
|
||||||
}
|
}
|
||||||
@ -38,18 +22,4 @@ mod tests {
|
|||||||
let ans = vec_loop(&input);
|
let ans = vec_loop(&input);
|
||||||
assert_eq!(ans, [4, 8, 12, 16, 20]);
|
assert_eq!(ans, [4, 8, 12, 16, 20]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_vec_map_example() {
|
|
||||||
let input = [1, 2, 3];
|
|
||||||
let ans = vec_map_example(&input);
|
|
||||||
assert_eq!(ans, [2, 3, 4]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_vec_map() {
|
|
||||||
let input = [2, 4, 6, 8, 10];
|
|
||||||
let ans = vec_map(&input);
|
|
||||||
assert_eq!(ans, [4, 8, 12, 16, 20]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,33 +1,21 @@
|
|||||||
|
#![deny(clippy::use_self)] // practice using the `Self` type
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Package {
|
struct Fireworks {
|
||||||
sender_country: String,
|
rockets: usize,
|
||||||
recipient_country: String,
|
|
||||||
weight_in_grams: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Package {
|
impl Fireworks {
|
||||||
fn new(sender_country: String, recipient_country: String, weight_in_grams: u32) -> Self {
|
fn new() -> Self {
|
||||||
if weight_in_grams < 10 {
|
Self { rockets: 0 }
|
||||||
// This isn't how you should handle errors in Rust, but we will
|
|
||||||
// learn about error handling later.
|
|
||||||
panic!("Can't ship a package with weight below 10 grams");
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
|
||||||
sender_country,
|
|
||||||
recipient_country,
|
|
||||||
weight_in_grams,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_international(&self) -> bool {
|
fn add_rockets(&mut self, rockets: usize) {
|
||||||
// ^^^^^^^ added
|
self.rockets += rockets
|
||||||
self.sender_country != self.recipient_country
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_fees(&self, cents_per_gram: u32) -> u32 {
|
fn start(self) -> String {
|
||||||
// ^^^^^^ added
|
"🚀".repeat(self.rockets)
|
||||||
self.weight_in_grams * cents_per_gram
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,44 +28,18 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
fn start_some_fireworks() {
|
||||||
fn fail_creating_weightless_package() {
|
let f = Fireworks::new();
|
||||||
let sender_country = String::from("Spain");
|
assert_eq!(f.start(), "");
|
||||||
let recipient_country = String::from("Austria");
|
|
||||||
|
|
||||||
Package::new(sender_country, recipient_country, 5);
|
let mut f = Fireworks::new();
|
||||||
}
|
f.add_rockets(3);
|
||||||
|
assert_eq!(f.start(), "🚀🚀🚀");
|
||||||
|
|
||||||
#[test]
|
let mut f = Fireworks::new();
|
||||||
fn create_international_package() {
|
f.add_rockets(7);
|
||||||
let sender_country = String::from("Spain");
|
// We don't use method syntax in the last test to ensure the `start`
|
||||||
let recipient_country = String::from("Russia");
|
// function takes ownership of the fireworks.
|
||||||
|
assert_eq!(Fireworks::start(f), "🚀🚀🚀🚀🚀🚀🚀");
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ fn main() {
|
|||||||
// `.into()` converts a type into an expected type.
|
// `.into()` converts a type into an expected type.
|
||||||
// If it is called where `String` is expected, it will convert `&str` to `String`.
|
// If it is called where `String` is expected, it will convert `&str` to `String`.
|
||||||
string("nice weather".into());
|
string("nice weather".into());
|
||||||
// But if it is called where `&str` is expected, then `&str` is kept `&str` since no conversion is needed.
|
// But if it is called where `&str` is expected, then `&str` is kept as `&str` since no conversion is needed.
|
||||||
// If you remove the `#[allow(…)]` line, then Clippy will tell you to remove `.into()` below since it is a useless conversion.
|
// If you remove the `#[allow(…)]` line, then Clippy will tell you to remove `.into()` below since it is a useless conversion.
|
||||||
#[allow(clippy::useless_conversion)]
|
#[allow(clippy::useless_conversion)]
|
||||||
string_slice("nice weather".into());
|
string_slice("nice weather".into());
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// A basket of fruits in the form of a hash map needs to be defined. The key
|
// A basket of fruits in the form of a hash map needs to be defined. The key
|
||||||
// represents the name of the fruit and the value represents how many of that
|
// represents the name of the fruit and the value represents how many of that
|
||||||
// particular fruit is in the basket. You have to put at least 3 different
|
// particular fruit is in the basket. You have to put at least 3 different
|
||||||
// types of fruits (e.g apple, banana, mango) in the basket and the total count
|
// types of fruits (e.g. apple, banana, mango) in the basket and the total count
|
||||||
// of all the fruits should be at least 5.
|
// of all the fruits should be at least 5.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
// This function returns how much icecream there is left in the fridge.
|
// This function returns how much ice cream there is left in the fridge.
|
||||||
// If it's before 22:00 (24-hour system), then 5 scoops are left. At 22:00,
|
// If it's before 22:00 (24-hour system), then 5 scoops are left. At 22:00,
|
||||||
// someone eats it all, so no icecream is left (value 0). Return `None` if
|
// someone eats it all, so no ice cream is left (value 0). Return `None` if
|
||||||
// `hour_of_day` is higher than 23.
|
// `hour_of_day` is higher than 23.
|
||||||
fn maybe_icecream(hour_of_day: u16) -> Option<u16> {
|
fn maybe_ice_cream(hour_of_day: u16) -> Option<u16> {
|
||||||
match hour_of_day {
|
match hour_of_day {
|
||||||
0..=21 => Some(5),
|
0..=21 => Some(5),
|
||||||
22..=23 => Some(0),
|
22..=23 => Some(0),
|
||||||
@ -21,19 +21,19 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn raw_value() {
|
fn raw_value() {
|
||||||
// Using `unwrap` is fine in a test.
|
// Using `unwrap` is fine in a test.
|
||||||
let icecreams = maybe_icecream(12).unwrap();
|
let ice_creams = maybe_ice_cream(12).unwrap();
|
||||||
|
|
||||||
assert_eq!(icecreams, 5);
|
assert_eq!(ice_creams, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_icecream() {
|
fn check_ice_cream() {
|
||||||
assert_eq!(maybe_icecream(0), Some(5));
|
assert_eq!(maybe_ice_cream(0), Some(5));
|
||||||
assert_eq!(maybe_icecream(9), Some(5));
|
assert_eq!(maybe_ice_cream(9), Some(5));
|
||||||
assert_eq!(maybe_icecream(18), Some(5));
|
assert_eq!(maybe_ice_cream(18), Some(5));
|
||||||
assert_eq!(maybe_icecream(22), Some(0));
|
assert_eq!(maybe_ice_cream(22), Some(0));
|
||||||
assert_eq!(maybe_icecream(23), Some(0));
|
assert_eq!(maybe_ice_cream(23), Some(0));
|
||||||
assert_eq!(maybe_icecream(24), None);
|
assert_eq!(maybe_ice_cream(24), None);
|
||||||
assert_eq!(maybe_icecream(25), None);
|
assert_eq!(maybe_ice_cream(25), None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
//
|
//
|
||||||
// In short, this particular use case for boxes is for when you want to own a
|
// In short, this particular use case for boxes is for when you want to own a
|
||||||
// value and you care only that it is a type which implements a particular
|
// value and you care only that it is a type which implements a particular
|
||||||
// trait. To do so, The `Box` is declared as of type `Box<dyn Trait>` where
|
// trait. To do so, the `Box` is declared as of type `Box<dyn Trait>` where
|
||||||
// `Trait` is the trait the compiler looks for on any value used in that
|
// `Trait` is the trait the compiler looks for on any value used in that
|
||||||
// context. For this exercise, that context is the potential errors which
|
// context. For this exercise, that context is the potential errors which
|
||||||
// can be returned in a `Result`.
|
// can be returned in a `Result`.
|
||||||
|
|||||||
@ -4,7 +4,7 @@ fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let string1 = String::from("long string is long");
|
let string1 = String::from("long string is long");
|
||||||
// Solution1: You can move `strings2` out of the inner block so that it is
|
// Solution 1: You can move `strings2` out of the inner block so that it is
|
||||||
// not dropped before the print statement.
|
// not dropped before the print statement.
|
||||||
let string2 = String::from("xyz");
|
let string2 = String::from("xyz");
|
||||||
let result;
|
let result;
|
||||||
@ -21,7 +21,7 @@ fn main() {
|
|||||||
{
|
{
|
||||||
let string2 = String::from("xyz");
|
let string2 = String::from("xyz");
|
||||||
result = longest(&string1, &string2);
|
result = longest(&string1, &string2);
|
||||||
// Solution2: You can move the print statement into the inner block so
|
// Solution 2: You can move the print statement into the inner block so
|
||||||
// that it is executed before `string2` is dropped.
|
// that it is executed before `string2` is dropped.
|
||||||
println!("The longest string is '{result}'");
|
println!("The longest string is '{result}'");
|
||||||
// `string2` dropped here (end of the inner scope).
|
// `string2` dropped here (end of the inner scope).
|
||||||
|
|||||||
@ -10,9 +10,9 @@ fn main() {
|
|||||||
mod tests {
|
mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn iterators() {
|
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();
|
let mut fav_fruits_iterator = my_fav_fruits.iter();
|
||||||
|
|
||||||
assert_eq!(fav_fruits_iterator.next(), Some(&"banana"));
|
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]
|
#[macro_use]
|
||||||
mod macros {
|
mod macros {
|
||||||
macro_rules! my_macro {
|
macro_rules! my_macro {
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
#[allow(unused_variables, unused_assignments)]
|
#[allow(unused_variables, unused_assignments)]
|
||||||
fn main() {
|
fn main() {
|
||||||
let my_option: Option<&str> = None;
|
let my_option: Option<&str> = None;
|
||||||
@ -11,17 +10,18 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A comma was missing.
|
// A comma was missing.
|
||||||
|
#[rustfmt::skip]
|
||||||
let my_arr = &[
|
let my_arr = &[
|
||||||
-1, -2, -3,
|
-1, -2, -3,
|
||||||
-4, -5, -6,
|
-4, -5, -6,
|
||||||
];
|
];
|
||||||
println!("My array! Here it is: {my_arr:?}");
|
println!("My array! Here it is: {my_arr:?}");
|
||||||
|
|
||||||
let mut my_empty_vec = vec![1, 2, 3, 4, 5];
|
let mut my_vec = vec![1, 2, 3, 4, 5];
|
||||||
// `resize` mutates a vector instead of returning a new one.
|
// `resize` mutates a vector instead of returning a new one.
|
||||||
// `resize(0, …)` clears a vector, so it is better to use `clear`.
|
// `resize(0, …)` clears a vector, so it is better to use `clear`.
|
||||||
my_empty_vec.clear();
|
my_vec.clear();
|
||||||
println!("This Vec is empty, see? {my_empty_vec:?}");
|
println!("This Vec is empty, see? {my_vec:?}");
|
||||||
|
|
||||||
let mut value_a = 45;
|
let mut value_a = 45;
|
||||||
let mut value_b = 66;
|
let mut value_b = 66;
|
||||||
|
|||||||
@ -2,7 +2,6 @@ use anyhow::{Context, Error, Result, bail};
|
|||||||
use crossterm::{QueueableCommand, cursor, terminal};
|
use crossterm::{QueueableCommand, cursor, terminal};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
env,
|
|
||||||
fs::{File, OpenOptions},
|
fs::{File, OpenOptions},
|
||||||
io::{Read, Seek, StdoutLock, Write},
|
io::{Read, Seek, StdoutLock, Write},
|
||||||
path::{MAIN_SEPARATOR_STR, Path},
|
path::{MAIN_SEPARATOR_STR, Path},
|
||||||
@ -17,6 +16,7 @@ use std::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
clear_terminal,
|
clear_terminal,
|
||||||
cmd::CmdRunner,
|
cmd::CmdRunner,
|
||||||
|
editor::{Editor, EditorJoinHandle},
|
||||||
embedded::EMBEDDED_FILES,
|
embedded::EMBEDDED_FILES,
|
||||||
exercise::{Exercise, RunnableExercise},
|
exercise::{Exercise, RunnableExercise},
|
||||||
info_file::ExerciseInfo,
|
info_file::ExerciseInfo,
|
||||||
@ -52,21 +52,24 @@ pub enum CheckProgress {
|
|||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
current_exercise_ind: usize,
|
current_exercise_ind: usize,
|
||||||
exercises: Vec<Exercise>,
|
exercises: Vec<Exercise>,
|
||||||
// Caches the number of done exercises to avoid iterating over all exercises every time.
|
// Cache the number of done exercises to avoid iterating over all exercises every time.
|
||||||
n_done: u16,
|
n_done: u32,
|
||||||
final_message: String,
|
final_message: &'static str,
|
||||||
state_file: File,
|
state_file: File,
|
||||||
// Preallocated buffer for reading and writing the state file.
|
// Preallocated buffer for reading and writing the state file.
|
||||||
file_buf: Vec<u8>,
|
file_buf: Vec<u8>,
|
||||||
official_exercises: bool,
|
official_exercises: bool,
|
||||||
cmd_runner: CmdRunner,
|
cmd_runner: CmdRunner,
|
||||||
emit_file_links: bool,
|
emit_file_links: bool,
|
||||||
|
editor: Option<Editor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
exercise_infos: Vec<ExerciseInfo>,
|
exercise_infos: Vec<ExerciseInfo>,
|
||||||
final_message: String,
|
final_message: &'static str,
|
||||||
|
editor: Option<Editor>,
|
||||||
|
vs_code_term: bool,
|
||||||
) -> Result<(Self, StateFileStatus)> {
|
) -> Result<(Self, StateFileStatus)> {
|
||||||
let cmd_runner = CmdRunner::build()?;
|
let cmd_runner = CmdRunner::build()?;
|
||||||
let mut state_file = OpenOptions::new()
|
let mut state_file = OpenOptions::new()
|
||||||
@ -83,43 +86,38 @@ impl AppState {
|
|||||||
let mut exercises = exercise_infos
|
let mut exercises = exercise_infos
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|exercise_info| {
|
.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 canonical_path = dir_canonical_path.as_deref().map(|dir_canonical_path| {
|
||||||
let mut canonical_path;
|
let mut canonical_path;
|
||||||
if let Some(dir) = dir {
|
if let Some(dir) = exercise_info.dir {
|
||||||
canonical_path = String::with_capacity(
|
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(dir_canonical_path);
|
||||||
canonical_path.push_str(MAIN_SEPARATOR_STR);
|
canonical_path.push_str(MAIN_SEPARATOR_STR);
|
||||||
canonical_path.push_str(dir);
|
canonical_path.push_str(dir);
|
||||||
} else {
|
} else {
|
||||||
canonical_path =
|
canonical_path = String::with_capacity(
|
||||||
String::with_capacity(1 + dir_canonical_path.len() + name.len());
|
1 + dir_canonical_path.len() + exercise_info.name.len(),
|
||||||
|
);
|
||||||
canonical_path.push_str(dir_canonical_path);
|
canonical_path.push_str(dir_canonical_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
canonical_path.push_str(MAIN_SEPARATOR_STR);
|
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.push_str(".rs");
|
||||||
canonical_path
|
canonical_path
|
||||||
});
|
});
|
||||||
|
|
||||||
Exercise {
|
Exercise {
|
||||||
dir,
|
name: exercise_info.name,
|
||||||
name,
|
dir: exercise_info.dir,
|
||||||
path,
|
// 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,
|
canonical_path,
|
||||||
test: exercise_info.test,
|
test: exercise_info.test,
|
||||||
strict_clippy: exercise_info.strict_clippy,
|
strict_clippy: exercise_info.strict_clippy,
|
||||||
hint,
|
hint: exercise_info.hint.trim_ascii(),
|
||||||
// Updated below.
|
// Updated below.
|
||||||
done: false,
|
done: false,
|
||||||
}
|
}
|
||||||
@ -181,43 +179,37 @@ impl AppState {
|
|||||||
official_exercises: !Path::new("info.toml").exists(),
|
official_exercises: !Path::new("info.toml").exists(),
|
||||||
cmd_runner,
|
cmd_runner,
|
||||||
// VS Code has its own file link handling
|
// 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))
|
Ok((slf, state_file_status))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn current_exercise_ind(&self) -> usize {
|
pub fn current_exercise_ind(&self) -> usize {
|
||||||
self.current_exercise_ind
|
self.current_exercise_ind
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn exercises(&self) -> &[Exercise] {
|
pub fn exercises(&self) -> &[Exercise] {
|
||||||
&self.exercises
|
&self.exercises
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
pub fn n_done(&self) -> u32 {
|
||||||
pub fn n_done(&self) -> u16 {
|
|
||||||
self.n_done
|
self.n_done
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
pub fn n_pending(&self) -> u32 {
|
||||||
pub fn n_pending(&self) -> u16 {
|
self.exercises.len() as u32 - self.n_done
|
||||||
self.exercises.len() as u16 - self.n_done
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn current_exercise(&self) -> &Exercise {
|
pub fn current_exercise(&self) -> &Exercise {
|
||||||
&self.exercises[self.current_exercise_ind]
|
&self.exercises[self.current_exercise_ind]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn cmd_runner(&self) -> &CmdRunner {
|
pub fn cmd_runner(&self) -> &CmdRunner {
|
||||||
&self.cmd_runner
|
&self.cmd_runner
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn emit_file_links(&self) -> bool {
|
pub fn emit_file_links(&self) -> bool {
|
||||||
self.emit_file_links
|
self.emit_file_links
|
||||||
}
|
}
|
||||||
@ -343,12 +335,10 @@ impl AppState {
|
|||||||
Ok(())
|
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)?;
|
self.set_pending(self.current_exercise_ind)?;
|
||||||
let exercise = self.current_exercise();
|
let exercise = self.current_exercise();
|
||||||
self.reset(self.current_exercise_ind, exercise.path)?;
|
self.reset(self.current_exercise_ind, exercise.path)
|
||||||
|
|
||||||
Ok(exercise.path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the exercise by index and return its name.
|
// Reset the exercise by index and return its name.
|
||||||
@ -440,7 +430,7 @@ impl AppState {
|
|||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
};
|
}
|
||||||
|
|
||||||
let success = exercise.run_exercise(None, &slf.cmd_runner);
|
let success = exercise.run_exercise(None, &slf.cmd_runner);
|
||||||
let progress = match success {
|
let progress = match success {
|
||||||
@ -567,6 +557,28 @@ impl AppState {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn open_editor(&mut self) -> Result<EditorJoinHandle> {
|
||||||
|
if let Some(editor) = self.editor.take() {
|
||||||
|
return editor.open(self.current_exercise_ind, self.current_exercise().path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(EditorJoinHandle::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn join_editor_handle(&mut self, handle: EditorJoinHandle) -> Result<()> {
|
||||||
|
self.editor = handle.join()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close_editor(&mut self) -> Result<()> {
|
||||||
|
if let Some(editor) = &mut self.editor {
|
||||||
|
editor.close()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const BAD_INDEX_ERR: &str = "The current exercise index is higher than the number of exercises";
|
const 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 {
|
fn dummy_exercise() -> Exercise {
|
||||||
Exercise {
|
Exercise {
|
||||||
dir: None,
|
|
||||||
name: "0",
|
name: "0",
|
||||||
|
dir: None,
|
||||||
path: "exercises/0.rs",
|
path: "exercises/0.rs",
|
||||||
canonical_path: None,
|
canonical_path: None,
|
||||||
test: false,
|
test: false,
|
||||||
@ -616,12 +628,13 @@ mod tests {
|
|||||||
current_exercise_ind: 0,
|
current_exercise_ind: 0,
|
||||||
exercises: vec![dummy_exercise(), dummy_exercise(), dummy_exercise()],
|
exercises: vec![dummy_exercise(), dummy_exercise(), dummy_exercise()],
|
||||||
n_done: 0,
|
n_done: 0,
|
||||||
final_message: String::new(),
|
final_message: "",
|
||||||
state_file: tempfile::tempfile().unwrap(),
|
state_file: tempfile::tempfile().unwrap(),
|
||||||
file_buf: Vec::new(),
|
file_buf: Vec::new(),
|
||||||
official_exercises: true,
|
official_exercises: true,
|
||||||
cmd_runner: CmdRunner::build().unwrap(),
|
cmd_runner: CmdRunner::build().unwrap(),
|
||||||
emit_file_links: true,
|
emit_file_links: true,
|
||||||
|
editor: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut assert = |done: [bool; 3], expected: [Option<usize>; 3]| {
|
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(b"\", path = \"");
|
||||||
buf.extend_from_slice(exercise_path_prefix);
|
buf.extend_from_slice(exercise_path_prefix);
|
||||||
buf.extend_from_slice(b"exercises/");
|
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.extend_from_slice(dir.as_bytes());
|
||||||
buf.push(b'/');
|
buf.push(b'/');
|
||||||
}
|
}
|
||||||
@ -56,7 +56,7 @@ pub fn append_bins(
|
|||||||
buf.extend_from_slice(b"\", path = \"");
|
buf.extend_from_slice(b"\", path = \"");
|
||||||
buf.extend_from_slice(exercise_path_prefix);
|
buf.extend_from_slice(exercise_path_prefix);
|
||||||
buf.extend_from_slice(b"solutions/");
|
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.extend_from_slice(dir.as_bytes());
|
||||||
buf.push(b'/');
|
buf.push(b'/');
|
||||||
}
|
}
|
||||||
@ -106,19 +106,19 @@ mod tests {
|
|||||||
fn test_bins() {
|
fn test_bins() {
|
||||||
let exercise_infos = [
|
let exercise_infos = [
|
||||||
ExerciseInfo {
|
ExerciseInfo {
|
||||||
name: String::from("1"),
|
name: "1",
|
||||||
dir: None,
|
dir: None,
|
||||||
test: true,
|
test: true,
|
||||||
strict_clippy: true,
|
strict_clippy: true,
|
||||||
hint: String::new(),
|
hint: "",
|
||||||
skip_check_unsolved: false,
|
skip_check_unsolved: false,
|
||||||
},
|
},
|
||||||
ExerciseInfo {
|
ExerciseInfo {
|
||||||
name: String::from("2"),
|
name: "2",
|
||||||
dir: Some(String::from("d")),
|
dir: Some("d"),
|
||||||
test: false,
|
test: false,
|
||||||
strict_clippy: false,
|
strict_clippy: false,
|
||||||
hint: String::new(),
|
hint: "",
|
||||||
skip_check_unsolved: false,
|
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<'_> {
|
impl CargoSubcommand<'_> {
|
||||||
#[inline]
|
|
||||||
pub fn args<'arg, I>(&mut self, args: I) -> &mut Self
|
pub fn args<'arg, I>(&mut self, args: I) -> &mut Self
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = &'arg str>,
|
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.
|
/// 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> {
|
pub fn run(self, description: &str) -> Result<bool> {
|
||||||
run_cmd(self.cmd, description, self.output)
|
run_cmd(self.cmd, description, self.output)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ mod new;
|
|||||||
mod update;
|
mod update;
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
pub enum DevCommands {
|
pub enum DevCommand {
|
||||||
/// Create a new project for community exercises
|
/// Create a new project for community exercises
|
||||||
New {
|
New {
|
||||||
/// The path to create the project in
|
/// The path to create the project in
|
||||||
@ -26,7 +26,7 @@ pub enum DevCommands {
|
|||||||
Update,
|
Update,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DevCommands {
|
impl DevCommand {
|
||||||
pub fn run(self) -> Result<()> {
|
pub fn run(self) -> Result<()> {
|
||||||
match self {
|
match self {
|
||||||
Self::New { path, no_git } => {
|
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);
|
let mut file_buf = String::with_capacity(1 << 14);
|
||||||
for exercise_info in &info_file.exercises {
|
for exercise_info in &info_file.exercises {
|
||||||
let name = exercise_info.name.as_str();
|
let name = exercise_info.name;
|
||||||
if name.is_empty() {
|
if name.is_empty() {
|
||||||
bail!("Found an empty exercise name in `info.toml`");
|
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");
|
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() {
|
if dir.is_empty() {
|
||||||
bail!("The exercise `{name}` has an empty dir name in `info.toml`");
|
bail!("The exercise `{name}` has an empty dir name in `info.toml`");
|
||||||
}
|
}
|
||||||
@ -214,7 +214,7 @@ fn check_exercises_unsolved(
|
|||||||
Some(
|
Some(
|
||||||
thread::Builder::new()
|
thread::Builder::new()
|
||||||
.spawn(|| exercise_info.run_exercise(None, cmd_runner))
|
.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<_>, _>>()
|
.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.truncate(prefix.len());
|
||||||
exercise_path.push_str(dir.name);
|
exercise_path.push_str(dir.name);
|
||||||
exercise_path.push('/');
|
exercise_path.push('/');
|
||||||
exercise_path.push_str(&exercise_info.name);
|
exercise_path.push_str(exercise_info.name);
|
||||||
exercise_path.push_str(".rs");
|
exercise_path.push_str(".rs");
|
||||||
|
|
||||||
fs::write(&exercise_path, exercise_files.exercise)
|
fs::write(&exercise_path, exercise_files.exercise)
|
||||||
@ -141,13 +141,14 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct ExerciseInfo {
|
struct ExerciseInfo<'a> {
|
||||||
dir: String,
|
dir: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct InfoFile {
|
struct InfoFile<'a> {
|
||||||
exercises: Vec<ExerciseInfo>,
|
#[serde(borrow)]
|
||||||
|
exercises: Vec<ExerciseInfo<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -66,8 +66,8 @@ fn run_bin(
|
|||||||
|
|
||||||
/// See `info_file::ExerciseInfo`
|
/// See `info_file::ExerciseInfo`
|
||||||
pub struct Exercise {
|
pub struct Exercise {
|
||||||
pub dir: Option<&'static str>,
|
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
|
pub dir: Option<&'static str>,
|
||||||
/// Path of the exercise file starting with the `exercises/` directory.
|
/// Path of the exercise file starting with the `exercises/` directory.
|
||||||
pub path: &'static str,
|
pub path: &'static str,
|
||||||
pub canonical_path: Option<String>,
|
pub canonical_path: Option<String>,
|
||||||
@ -158,7 +158,6 @@ pub trait RunnableExercise {
|
|||||||
|
|
||||||
/// Compile, check and run the exercise.
|
/// Compile, check and run the exercise.
|
||||||
/// The output is written to the `output` buffer after clearing it.
|
/// 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> {
|
fn run_exercise(&self, output: Option<&mut Vec<u8>>, cmd_runner: &CmdRunner) -> Result<bool> {
|
||||||
self.run::<false>(self.name(), output, cmd_runner)
|
self.run::<false>(self.name(), output, cmd_runner)
|
||||||
}
|
}
|
||||||
@ -201,22 +200,18 @@ pub trait RunnableExercise {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RunnableExercise for Exercise {
|
impl RunnableExercise for Exercise {
|
||||||
#[inline]
|
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
self.name
|
self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn dir(&self) -> Option<&str> {
|
fn dir(&self) -> Option<&str> {
|
||||||
self.dir
|
self.dir
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn strict_clippy(&self) -> bool {
|
fn strict_clippy(&self) -> bool {
|
||||||
self.strict_clippy
|
self.strict_clippy
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn test(&self) -> bool {
|
fn test(&self) -> bool {
|
||||||
self.test
|
self.test
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,9 +8,9 @@ use crate::{embedded::EMBEDDED_FILES, exercise::RunnableExercise};
|
|||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct ExerciseInfo {
|
pub struct ExerciseInfo {
|
||||||
/// Exercise's unique name.
|
/// Exercise's unique name.
|
||||||
pub name: String,
|
pub name: &'static str,
|
||||||
/// Exercise's directory name inside the `exercises/` directory.
|
/// Exercise's directory name inside the `exercises/` directory.
|
||||||
pub dir: Option<String>,
|
pub dir: Option<&'static str>,
|
||||||
/// Run `cargo test` on the exercise.
|
/// Run `cargo test` on the exercise.
|
||||||
#[serde(default = "default_true")]
|
#[serde(default = "default_true")]
|
||||||
pub test: bool,
|
pub test: bool,
|
||||||
@ -18,12 +18,11 @@ pub struct ExerciseInfo {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub strict_clippy: bool,
|
pub strict_clippy: bool,
|
||||||
/// The exercise's hint to be shown to the user on request.
|
/// 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.
|
/// The exercise is already solved. Ignore it when checking that all exercises are unsolved.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub skip_check_unsolved: bool,
|
pub skip_check_unsolved: bool,
|
||||||
}
|
}
|
||||||
#[inline(always)]
|
|
||||||
const fn default_true() -> bool {
|
const fn default_true() -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@ -31,7 +30,7 @@ const fn default_true() -> bool {
|
|||||||
impl ExerciseInfo {
|
impl ExerciseInfo {
|
||||||
/// Path to the exercise file starting with the `exercises/` directory.
|
/// Path to the exercise file starting with the `exercises/` directory.
|
||||||
pub fn path(&self) -> String {
|
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
|
// 14 = 10 + 1 + 3
|
||||||
// exercises/ + / + .rs
|
// exercises/ + / + .rs
|
||||||
let mut path = String::with_capacity(14 + dir.len() + self.name.len());
|
let mut path = String::with_capacity(14 + dir.len() + self.name.len());
|
||||||
@ -47,7 +46,7 @@ impl ExerciseInfo {
|
|||||||
path
|
path
|
||||||
};
|
};
|
||||||
|
|
||||||
path.push_str(&self.name);
|
path.push_str(self.name);
|
||||||
path.push_str(".rs");
|
path.push_str(".rs");
|
||||||
|
|
||||||
path
|
path
|
||||||
@ -55,22 +54,18 @@ impl ExerciseInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RunnableExercise for ExerciseInfo {
|
impl RunnableExercise for ExerciseInfo {
|
||||||
#[inline]
|
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
&self.name
|
self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn dir(&self) -> Option<&str> {
|
fn dir(&self) -> Option<&str> {
|
||||||
self.dir.as_deref()
|
self.dir
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn strict_clippy(&self) -> bool {
|
fn strict_clippy(&self) -> bool {
|
||||||
self.strict_clippy
|
self.strict_clippy
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn test(&self) -> bool {
|
fn test(&self) -> bool {
|
||||||
self.test
|
self.test
|
||||||
}
|
}
|
||||||
@ -82,9 +77,9 @@ pub struct InfoFile {
|
|||||||
/// For possible breaking changes in the future for community exercises.
|
/// For possible breaking changes in the future for community exercises.
|
||||||
pub format_version: u8,
|
pub format_version: u8,
|
||||||
/// Shown to users when starting with the exercises.
|
/// Shown to users when starting with the exercises.
|
||||||
pub welcome_message: Option<String>,
|
pub welcome_message: Option<&'static str>,
|
||||||
/// Shown to users after finishing all exercises.
|
/// Shown to users after finishing all exercises.
|
||||||
pub final_message: Option<String>,
|
pub final_message: Option<&'static str>,
|
||||||
/// List of all exercises.
|
/// List of all exercises.
|
||||||
pub exercises: Vec<ExerciseInfo>,
|
pub exercises: Vec<ExerciseInfo>,
|
||||||
}
|
}
|
||||||
@ -94,9 +89,17 @@ impl InfoFile {
|
|||||||
/// Community exercises: Parse the `info.toml` file in the current directory.
|
/// Community exercises: Parse the `info.toml` file in the current directory.
|
||||||
pub fn parse() -> Result<Self> {
|
pub fn parse() -> Result<Self> {
|
||||||
// Read a local `info.toml` if it exists.
|
// Read a local `info.toml` if it exists.
|
||||||
let slf = match fs::read_to_string("info.toml") {
|
let slf = match fs::read("info.toml") {
|
||||||
Ok(file_content) => toml::de::from_str::<Self>(&file_content)
|
Ok(file_content) => {
|
||||||
.context("Failed to parse the `info.toml` file")?,
|
// 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) => {
|
Err(e) => {
|
||||||
if e.kind() == ErrorKind::NotFound {
|
if e.kind() == ErrorKind::NotFound {
|
||||||
return toml::de::from_str(EMBEDDED_FILES.info_file)
|
return toml::de::from_str(EMBEDDED_FILES.info_file)
|
||||||
|
|||||||
44
src/init.rs
44
src/init.rs
@ -5,10 +5,10 @@ use crossterm::{
|
|||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{
|
use std::{
|
||||||
env::set_current_dir,
|
env::{current_dir, set_current_dir},
|
||||||
fs::{self, create_dir},
|
fs::{self, create_dir},
|
||||||
io::{self, Write},
|
io::{self, Write},
|
||||||
path::{Path, PathBuf},
|
path::Path,
|
||||||
process::{Command, Stdio},
|
process::{Command, Stdio},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -18,8 +18,9 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct CargoLocateProject {
|
struct CargoLocateProject<'a> {
|
||||||
root: PathBuf,
|
#[serde(borrow)]
|
||||||
|
root: &'a Path,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init() -> Result<()> {
|
pub fn init() -> Result<()> {
|
||||||
@ -72,9 +73,9 @@ pub fn init() -> Result<()> {
|
|||||||
)?
|
)?
|
||||||
.root;
|
.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()))?;
|
.with_context(|| format!("Failed to read the file {}", workspace_manifest.display()))?;
|
||||||
if !workspace_manifest_content.contains("[workspace]\n")
|
if !workspace_manifest_content.contains("[workspace]")
|
||||||
&& !workspace_manifest_content.contains("workspace.")
|
&& !workspace_manifest_content.contains("workspace.")
|
||||||
{
|
{
|
||||||
bail!(
|
bail!(
|
||||||
@ -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")?;
|
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/`")?;
|
.context("Failed to remove the temporary directory `rustlings/`")?;
|
||||||
init_git = false;
|
init_git = false;
|
||||||
} else {
|
} else {
|
||||||
@ -168,14 +169,27 @@ pub fn init() -> Result<()> {
|
|||||||
fs::write(".vscode/extensions.json", VS_CODE_EXTENSIONS_JSON)
|
fs::write(".vscode/extensions.json", VS_CODE_EXTENSIONS_JSON)
|
||||||
.context("Failed to create the file `rustlings/.vscode/extensions.json`")?;
|
.context("Failed to create the file `rustlings/.vscode/extensions.json`")?;
|
||||||
|
|
||||||
if init_git {
|
if init_git && let Ok(dir) = current_dir() {
|
||||||
// Ignore any Git error because Git initialization is not required.
|
let mut dir = dir.as_path();
|
||||||
let _ = Command::new("git")
|
|
||||||
.arg("init")
|
loop {
|
||||||
.stdin(Stdio::null())
|
if dir.join(".git").exists() || dir.join(".jj").exists() {
|
||||||
.stdout(Stdio::null())
|
break;
|
||||||
.stderr(Stdio::null())
|
}
|
||||||
.status();
|
|
||||||
|
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")
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.status();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout.queue(SetForegroundColor(Color::Green))?;
|
stdout.queue(SetForegroundColor(Color::Green))?;
|
||||||
|
|||||||
@ -11,9 +11,10 @@ use crossterm::{
|
|||||||
};
|
};
|
||||||
use std::io::{self, StdoutLock, Write};
|
use std::io::{self, StdoutLock, Write};
|
||||||
|
|
||||||
use crate::app_state::AppState;
|
use crate::{
|
||||||
|
app_state::AppState,
|
||||||
use self::state::{Filter, ListState};
|
list::state::{Filter, ListState},
|
||||||
|
};
|
||||||
|
|
||||||
mod scroll_state;
|
mod scroll_state;
|
||||||
mod 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('r') => list_state.reset_selected()?,
|
||||||
KeyCode::Char('c') => {
|
KeyCode::Char('c') | KeyCode::Enter => {
|
||||||
if list_state.selected_to_current_exercise()? {
|
if list_state.selected_to_current_exercise()? {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,6 @@ impl ScrollState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn offset(&self) -> usize {
|
pub fn offset(&self) -> usize {
|
||||||
self.offset
|
self.offset
|
||||||
}
|
}
|
||||||
@ -41,7 +40,6 @@ impl ScrollState {
|
|||||||
.min(global_max_offset);
|
.min(global_max_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn selected(&self) -> Option<usize> {
|
pub fn selected(&self) -> Option<usize> {
|
||||||
self.selected
|
self.selected
|
||||||
}
|
}
|
||||||
@ -86,12 +84,10 @@ impl ScrollState {
|
|||||||
self.set_selected(self.selected.map_or(0, |selected| selected.min(n_rows - 1)));
|
self.set_selected(self.selected.map_or(0, |selected| selected.min(n_rows - 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn update_scroll_padding(&mut self) {
|
fn update_scroll_padding(&mut self) {
|
||||||
self.scroll_padding = (self.max_n_rows_to_display / 4).min(self.max_scroll_padding);
|
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 {
|
pub fn max_n_rows_to_display(&self) -> usize {
|
||||||
self.max_n_rows_to_display
|
self.max_n_rows_to_display
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,11 +15,10 @@ use std::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
app_state::AppState,
|
app_state::AppState,
|
||||||
exercise::Exercise,
|
exercise::Exercise,
|
||||||
|
list::scroll_state::ScrollState,
|
||||||
term::{CountedWrite, MaxLenWriter, progress_bar},
|
term::{CountedWrite, MaxLenWriter, progress_bar},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::scroll_state::ScrollState;
|
|
||||||
|
|
||||||
const COL_SPACING: usize = 2;
|
const COL_SPACING: usize = 2;
|
||||||
const SELECTED_ROW_ATTRIBUTES: Attributes = Attributes::none()
|
const SELECTED_ROW_ATTRIBUTES: Attributes = Attributes::none()
|
||||||
.with(Attribute::Reverse)
|
.with(Attribute::Reverse)
|
||||||
@ -229,7 +228,7 @@ impl<'a> ListState<'a> {
|
|||||||
progress_bar(
|
progress_bar(
|
||||||
&mut MaxLenWriter::new(stdout, self.term_width as usize),
|
&mut MaxLenWriter::new(stdout, self.term_width as usize),
|
||||||
self.app_state.n_done(),
|
self.app_state.n_done(),
|
||||||
self.app_state.exercises().len() as u16,
|
self.app_state.exercises().len() as u32,
|
||||||
self.term_width,
|
self.term_width,
|
||||||
)?;
|
)?;
|
||||||
next_ln(stdout)?;
|
next_ln(stdout)?;
|
||||||
@ -238,11 +237,24 @@ impl<'a> ListState<'a> {
|
|||||||
if self.message.is_empty() {
|
if self.message.is_empty() {
|
||||||
// Help footer message
|
// Help footer message
|
||||||
if self.scroll_state.selected().is_some() {
|
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)?;
|
next_ln(stdout)?;
|
||||||
writer = MaxLenWriter::new(stdout, self.term_width as usize);
|
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 {
|
} else {
|
||||||
// Nothing selected (and nothing shown), so only display filter and quit.
|
// Nothing selected (and nothing shown), so only display filter and quit.
|
||||||
writer.write_ascii(b"filter ")?;
|
writer.write_ascii(b"filter ")?;
|
||||||
@ -250,27 +262,41 @@ impl<'a> ListState<'a> {
|
|||||||
|
|
||||||
match self.filter {
|
match self.filter {
|
||||||
Filter::Done => {
|
Filter::Done => {
|
||||||
|
writer.stdout.queue(SetAttribute(Attribute::Underlined))?;
|
||||||
|
hotkey(&mut writer, b"d")?;
|
||||||
writer
|
writer
|
||||||
.stdout
|
.stdout
|
||||||
.queue(SetForegroundColor(Color::Magenta))?
|
.queue(SetForegroundColor(Color::Magenta))?
|
||||||
.queue(SetAttribute(Attribute::Underlined))?;
|
.queue(SetAttribute(Attribute::Underlined))?;
|
||||||
writer.write_ascii(b"<d>one")?;
|
writer.write_str("one")?;
|
||||||
writer.stdout.queue(ResetColor)?;
|
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 => {
|
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
|
writer
|
||||||
.stdout
|
.stdout
|
||||||
.queue(SetForegroundColor(Color::Magenta))?
|
.queue(SetForegroundColor(Color::Magenta))?
|
||||||
.queue(SetAttribute(Attribute::Underlined))?;
|
.queue(SetAttribute(Attribute::Underlined))?;
|
||||||
writer.write_ascii(b"<p>ending")?;
|
writer.write_ascii(b"ending")?;
|
||||||
writer.stdout.queue(ResetColor)?;
|
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 {
|
} else {
|
||||||
writer.stdout.queue(SetForegroundColor(Color::Magenta))?;
|
writer.stdout.queue(SetForegroundColor(Color::Magenta))?;
|
||||||
writer.write_str(&self.message)?;
|
writer.write_str(&self.message)?;
|
||||||
@ -304,7 +330,6 @@ impl<'a> ListState<'a> {
|
|||||||
self.scroll_state.set_n_rows(n_rows);
|
self.scroll_state.set_n_rows(n_rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn filter(&self) -> Filter {
|
pub fn filter(&self) -> Filter {
|
||||||
self.filter
|
self.filter
|
||||||
}
|
}
|
||||||
@ -314,22 +339,18 @@ impl<'a> ListState<'a> {
|
|||||||
self.update_rows();
|
self.update_rows();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn select_next(&mut self) {
|
pub fn select_next(&mut self) {
|
||||||
self.scroll_state.select_next();
|
self.scroll_state.select_next();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn select_previous(&mut self) {
|
pub fn select_previous(&mut self) {
|
||||||
self.scroll_state.select_previous();
|
self.scroll_state.select_previous();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn select_first(&mut self) {
|
pub fn select_first(&mut self) {
|
||||||
self.scroll_state.select_first();
|
self.scroll_state.select_first();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn select_last(&mut self) {
|
pub fn select_last(&mut self) {
|
||||||
self.scroll_state.select_last();
|
self.scroll_state.select_last();
|
||||||
}
|
}
|
||||||
@ -366,11 +387,11 @@ impl<'a> ListState<'a> {
|
|||||||
|
|
||||||
let exercise_ind = self.selected_to_exercise_ind(selected)?;
|
let exercise_ind = self.selected_to_exercise_ind(selected)?;
|
||||||
let exercise_name = self.app_state.reset_exercise_by_ind(exercise_ind)?;
|
let exercise_name = self.app_state.reset_exercise_by_ind(exercise_ind)?;
|
||||||
self.update_rows();
|
|
||||||
write!(
|
write!(
|
||||||
self.message,
|
self.message,
|
||||||
"The exercise `{exercise_name}` has been reset",
|
"The exercise `{exercise_name}` has been reset",
|
||||||
)?;
|
)?;
|
||||||
|
self.update_rows();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -415,3 +436,14 @@ impl<'a> ListState<'a> {
|
|||||||
Ok(true)
|
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 anyhow::{Context, Result, bail};
|
||||||
use app_state::StateFileStatus;
|
use app_state::StateFileStatus;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::Parser;
|
||||||
use std::{
|
use std::{
|
||||||
|
env,
|
||||||
io::{self, IsTerminal, Write},
|
io::{self, IsTerminal, Write},
|
||||||
path::Path,
|
path::Path,
|
||||||
process::ExitCode,
|
process::ExitCode,
|
||||||
};
|
};
|
||||||
use term::{clear_terminal, press_enter_prompt};
|
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 app_state;
|
||||||
mod cargo_toml;
|
mod cargo_toml;
|
||||||
|
mod cli;
|
||||||
mod cmd;
|
mod cmd;
|
||||||
mod dev;
|
mod dev;
|
||||||
|
mod editor;
|
||||||
mod embedded;
|
mod embedded;
|
||||||
mod exercise;
|
mod exercise;
|
||||||
mod info_file;
|
mod info_file;
|
||||||
@ -25,44 +33,6 @@ mod watch;
|
|||||||
|
|
||||||
const CURRENT_FORMAT_VERSION: u8 = 1;
|
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> {
|
fn main() -> Result<ExitCode> {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
@ -72,8 +42,8 @@ fn main() -> Result<ExitCode> {
|
|||||||
|
|
||||||
'priority_cmd: {
|
'priority_cmd: {
|
||||||
match args.command {
|
match args.command {
|
||||||
Some(Subcommands::Init) => init::init().context("Initialization failed")?,
|
Some(Command::Init) => init::init().context("Initialization failed")?,
|
||||||
Some(Subcommands::Dev(dev_command)) => dev_command.run()?,
|
Some(Command::Dev(dev_command)) => dev_command.run()?,
|
||||||
_ => break 'priority_cmd,
|
_ => break 'priority_cmd,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,9 +61,18 @@ fn main() -> Result<ExitCode> {
|
|||||||
bail!(FORMAT_VERSION_HIGHER_ERR);
|
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(
|
let (mut app_state, state_file_status) = AppState::new(
|
||||||
info_file.exercises,
|
info_file.exercises,
|
||||||
info_file.final_message.unwrap_or_default(),
|
info_file.final_message.unwrap_or_default(),
|
||||||
|
editor,
|
||||||
|
vs_code_term,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Show the welcome message if the state file doesn't exist yet.
|
// Show the welcome message if the state file doesn't exist yet.
|
||||||
@ -128,7 +107,7 @@ fn main() -> Result<ExitCode> {
|
|||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
// For the notify event handler thread.
|
// 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(
|
Some(
|
||||||
&*app_state
|
&*app_state
|
||||||
.exercises()
|
.exercises()
|
||||||
@ -140,14 +119,15 @@ fn main() -> Result<ExitCode> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
watch::watch(&mut app_state, notify_exercise_names)?;
|
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 {
|
if let Some(name) = name {
|
||||||
app_state.set_current_exercise_by_name(&name)?;
|
app_state.set_current_exercise_by_name(&name)?;
|
||||||
}
|
}
|
||||||
return run::run(&mut app_state);
|
return run::run(&mut app_state);
|
||||||
}
|
}
|
||||||
Some(Subcommands::CheckAll) => {
|
Some(Command::CheckAll) => {
|
||||||
let mut stdout = io::stdout().lock();
|
let mut stdout = io::stdout().lock();
|
||||||
if let Some(first_pending_exercise_ind) = app_state.check_all_exercises(&mut stdout)? {
|
if let Some(first_pending_exercise_ind) = app_state.check_all_exercises(&mut stdout)? {
|
||||||
if app_state.current_exercise().done {
|
if app_state.current_exercise().done {
|
||||||
@ -171,23 +151,36 @@ fn main() -> Result<ExitCode> {
|
|||||||
stdout.write_all(b"\n")?;
|
stdout.write_all(b"\n")?;
|
||||||
|
|
||||||
return Ok(ExitCode::FAILURE);
|
return Ok(ExitCode::FAILURE);
|
||||||
} else {
|
|
||||||
app_state.render_final_message(&mut stdout)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app_state.render_final_message(&mut stdout)?;
|
||||||
}
|
}
|
||||||
Some(Subcommands::Reset { name }) => {
|
Some(Command::Reset { name }) => {
|
||||||
app_state.set_current_exercise_by_name(&name)?;
|
app_state.set_current_exercise_by_name(&name)?;
|
||||||
let exercise_path = app_state.reset_current_exercise()?;
|
app_state.reset_current_exercise()?;
|
||||||
println!("The exercise {exercise_path} has been reset");
|
|
||||||
|
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 {
|
if let Some(name) = name {
|
||||||
app_state.set_current_exercise_by_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.
|
// Handled in an earlier match.
|
||||||
Some(Subcommands::Init | Subcommands::Dev(_)) => (),
|
Some(Command::Init | Command::Dev(_)) => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ExitCode::SUCCESS)
|
Ok(ExitCode::SUCCESS)
|
||||||
|
|||||||
32
src/term.rs
32
src/term.rs
@ -18,7 +18,6 @@ pub struct MaxLenWriter<'a, 'lock> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'lock> MaxLenWriter<'a, 'lock> {
|
impl<'a, 'lock> MaxLenWriter<'a, 'lock> {
|
||||||
#[inline]
|
|
||||||
pub fn new(stdout: &'a mut StdoutLock<'lock>, max_len: usize) -> Self {
|
pub fn new(stdout: &'a mut StdoutLock<'lock>, max_len: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
stdout,
|
stdout,
|
||||||
@ -28,7 +27,6 @@ impl<'a, 'lock> MaxLenWriter<'a, 'lock> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Additional is for emojis that take more space.
|
// Additional is for emojis that take more space.
|
||||||
#[inline]
|
|
||||||
pub fn add_to_len(&mut self, additional: usize) {
|
pub fn add_to_len(&mut self, additional: usize) {
|
||||||
self.len += additional;
|
self.len += additional;
|
||||||
}
|
}
|
||||||
@ -64,24 +62,20 @@ impl<'lock> CountedWrite<'lock> for MaxLenWriter<'_, 'lock> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn stdout(&mut self) -> &mut StdoutLock<'lock> {
|
fn stdout(&mut self) -> &mut StdoutLock<'lock> {
|
||||||
self.stdout
|
self.stdout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CountedWrite<'a> for StdoutLock<'a> {
|
impl<'a> CountedWrite<'a> for StdoutLock<'a> {
|
||||||
#[inline]
|
|
||||||
fn write_ascii(&mut self, ascii: &[u8]) -> io::Result<()> {
|
fn write_ascii(&mut self, ascii: &[u8]) -> io::Result<()> {
|
||||||
self.write_all(ascii)
|
self.write_all(ascii)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn write_str(&mut self, unicode: &str) -> io::Result<()> {
|
fn write_str(&mut self, unicode: &str) -> io::Result<()> {
|
||||||
self.write_all(unicode.as_bytes())
|
self.write_all(unicode.as_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn stdout(&mut self) -> &mut StdoutLock<'a> {
|
fn stdout(&mut self) -> &mut StdoutLock<'a> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -193,19 +187,19 @@ impl Drop for ProgressCounter<'_, '_> {
|
|||||||
|
|
||||||
pub fn progress_bar<'a>(
|
pub fn progress_bar<'a>(
|
||||||
writer: &mut impl CountedWrite<'a>,
|
writer: &mut impl CountedWrite<'a>,
|
||||||
progress: u16,
|
progress: u32,
|
||||||
total: u16,
|
total: u32,
|
||||||
term_width: u16,
|
term_width: u16,
|
||||||
) -> io::Result<()> {
|
) -> io::Result<()> {
|
||||||
debug_assert!(total <= 999);
|
|
||||||
debug_assert!(progress <= total);
|
|
||||||
|
|
||||||
const PREFIX: &[u8] = b"Progress: [";
|
const PREFIX: &[u8] = b"Progress: [";
|
||||||
const PREFIX_WIDTH: u16 = PREFIX.len() as u16;
|
const PREFIX_WIDTH: u16 = PREFIX.len() as u16;
|
||||||
const POSTFIX_WIDTH: u16 = "] xxx/xxx".len() as u16;
|
const POSTFIX_WIDTH: u16 = "] xxx/xxx".len() as u16;
|
||||||
const WRAPPER_WIDTH: u16 = PREFIX_WIDTH + POSTFIX_WIDTH;
|
const WRAPPER_WIDTH: u16 = PREFIX_WIDTH + POSTFIX_WIDTH;
|
||||||
const MIN_LINE_WIDTH: u16 = WRAPPER_WIDTH + 4;
|
const MIN_LINE_WIDTH: u16 = WRAPPER_WIDTH + 4;
|
||||||
|
|
||||||
|
debug_assert!(total <= 999);
|
||||||
|
debug_assert!(progress <= total);
|
||||||
|
|
||||||
if term_width < MIN_LINE_WIDTH {
|
if term_width < MIN_LINE_WIDTH {
|
||||||
writer.write_ascii(b"Progress: ")?;
|
writer.write_ascii(b"Progress: ")?;
|
||||||
// Integers are in ASCII.
|
// Integers are in ASCII.
|
||||||
@ -215,7 +209,8 @@ pub fn progress_bar<'a>(
|
|||||||
let stdout = writer.stdout();
|
let stdout = writer.stdout();
|
||||||
stdout.write_all(PREFIX)?;
|
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;
|
let filled = (width * progress) / total;
|
||||||
|
|
||||||
stdout.queue(SetForegroundColor(Color::Green))?;
|
stdout.queue(SetForegroundColor(Color::Green))?;
|
||||||
@ -225,14 +220,13 @@ pub fn progress_bar<'a>(
|
|||||||
|
|
||||||
if filled < width {
|
if filled < width {
|
||||||
stdout.write_all(b">")?;
|
stdout.write_all(b">")?;
|
||||||
}
|
|
||||||
|
|
||||||
let width_minus_filled = width - filled;
|
let width_minus_filled = width - filled;
|
||||||
if width_minus_filled > 1 {
|
if width_minus_filled > 1 {
|
||||||
let red_part_width = width_minus_filled - 1;
|
stdout.queue(SetForegroundColor(Color::Red))?;
|
||||||
stdout.queue(SetForegroundColor(Color::Red))?;
|
for _ in 1..width_minus_filled {
|
||||||
for _ in 0..red_part_width {
|
stdout.write_all(b"-")?;
|
||||||
stdout.write_all(b"-")?;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,10 +13,9 @@ use std::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
app_state::{AppState, ExercisesProgress},
|
app_state::{AppState, ExercisesProgress},
|
||||||
list,
|
list,
|
||||||
|
watch::{notify_event::NotifyEventHandler, state::WatchState, terminal_event::InputEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::{notify_event::NotifyEventHandler, state::WatchState, terminal_event::InputEvent};
|
|
||||||
|
|
||||||
mod notify_event;
|
mod notify_event;
|
||||||
mod state;
|
mod state;
|
||||||
mod terminal_event;
|
mod terminal_event;
|
||||||
@ -28,7 +27,6 @@ static EXERCISE_RUNNING: AtomicBool = AtomicBool::new(false);
|
|||||||
pub struct InputPauseGuard(());
|
pub struct InputPauseGuard(());
|
||||||
|
|
||||||
impl InputPauseGuard {
|
impl InputPauseGuard {
|
||||||
#[inline]
|
|
||||||
pub fn scoped_pause() -> Self {
|
pub fn scoped_pause() -> Self {
|
||||||
EXERCISE_RUNNING.store(true, Relaxed);
|
EXERCISE_RUNNING.store(true, Relaxed);
|
||||||
Self(())
|
Self(())
|
||||||
@ -36,7 +34,6 @@ impl InputPauseGuard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for InputPauseGuard {
|
impl Drop for InputPauseGuard {
|
||||||
#[inline]
|
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
EXERCISE_RUNNING.store(false, Relaxed);
|
EXERCISE_RUNNING.store(false, Relaxed);
|
||||||
}
|
}
|
||||||
@ -175,8 +172,7 @@ pub fn watch(
|
|||||||
watch_list_loop(app_state, notify_exercise_names)
|
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!
|
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.
|
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,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{EXERCISE_RUNNING, WatchEvent};
|
use crate::watch::{EXERCISE_RUNNING, WatchEvent};
|
||||||
|
|
||||||
const DEBOUNCE_DURATION: Duration = Duration::from_millis(200);
|
const DEBOUNCE_DURATION: Duration = Duration::from_millis(200);
|
||||||
|
|
||||||
|
|||||||
@ -17,10 +17,9 @@ use crate::{
|
|||||||
clear_terminal,
|
clear_terminal,
|
||||||
exercise::{OUTPUT_CAPACITY, RunnableExercise, solution_link_line},
|
exercise::{OUTPUT_CAPACITY, RunnableExercise, solution_link_line},
|
||||||
term::progress_bar,
|
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()
|
const HEADING_ATTRIBUTES: Attributes = Attributes::none()
|
||||||
.with(Attribute::Bold)
|
.with(Attribute::Bold)
|
||||||
.with(Attribute::Underlined);
|
.with(Attribute::Underlined);
|
||||||
@ -60,7 +59,7 @@ impl<'a> WatchState<'a> {
|
|||||||
watch_event_sender,
|
watch_event_sender,
|
||||||
terminal_event_unpause_receiver,
|
terminal_event_unpause_receiver,
|
||||||
manual_run,
|
manual_run,
|
||||||
)
|
);
|
||||||
})
|
})
|
||||||
.context("Failed to spawn a thread to handle terminal events")?;
|
.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.
|
// Ignore any input until running the exercise is done.
|
||||||
let _input_pause_guard = InputPauseGuard::scoped_pause();
|
let _input_pause_guard = InputPauseGuard::scoped_pause();
|
||||||
|
|
||||||
self.show_hint = false;
|
|
||||||
|
|
||||||
writeln!(
|
writeln!(
|
||||||
stdout,
|
stdout,
|
||||||
"\nChecking the exercise `{}`. Please wait…",
|
"\nChecking the exercise `{}`. Please wait…",
|
||||||
self.app_state.current_exercise().name,
|
self.app_state.current_exercise().name,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
let editor_handle = self.app_state.open_editor()?;
|
||||||
|
|
||||||
|
self.show_hint = false;
|
||||||
|
|
||||||
let success = self
|
let success = self
|
||||||
.app_state
|
.app_state
|
||||||
.current_exercise()
|
.current_exercise()
|
||||||
@ -106,7 +107,9 @@ impl<'a> WatchState<'a> {
|
|||||||
self.done_status = DoneStatus::Pending;
|
self.done_status = DoneStatus::Pending;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.app_state.join_editor_handle(editor_handle)?;
|
||||||
self.render(stdout)?;
|
self.render(stdout)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,9 +131,10 @@ impl<'a> WatchState<'a> {
|
|||||||
|
|
||||||
match answer[0] {
|
match answer[0] {
|
||||||
b'y' | b'Y' => {
|
b'y' | b'Y' => {
|
||||||
|
self.app_state.close_editor()?;
|
||||||
self.app_state.reset_current_exercise()?;
|
self.app_state.reset_current_exercise()?;
|
||||||
|
|
||||||
// The file watcher reruns the exercise otherwise.
|
// The file watcher reruns the exercise otherwise
|
||||||
if self.manual_run {
|
if self.manual_run {
|
||||||
self.run_current_exercise(stdout)?;
|
self.run_current_exercise(stdout)?;
|
||||||
}
|
}
|
||||||
@ -245,7 +249,7 @@ impl<'a> WatchState<'a> {
|
|||||||
progress_bar(
|
progress_bar(
|
||||||
stdout,
|
stdout,
|
||||||
self.app_state.n_done(),
|
self.app_state.n_done(),
|
||||||
self.app_state.exercises().len() as u16,
|
self.app_state.exercises().len() as u32,
|
||||||
self.term_width,
|
self.term_width,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ use std::sync::{
|
|||||||
mpsc::{Receiver, Sender},
|
mpsc::{Receiver, Sender},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{EXERCISE_RUNNING, WatchEvent};
|
use crate::watch::{EXERCISE_RUNNING, WatchEvent};
|
||||||
|
|
||||||
pub enum InputEvent {
|
pub enum InputEvent {
|
||||||
Next,
|
Next,
|
||||||
@ -47,7 +47,7 @@ pub fn terminal_event_handler(
|
|||||||
// Pause input until quitting the confirmation prompt.
|
// Pause input until quitting the confirmation prompt.
|
||||||
if unpause_receiver.recv().is_err() {
|
if unpause_receiver.recv().is_err() {
|
||||||
return;
|
return;
|
||||||
};
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -64,7 +64,7 @@ pub fn terminal_event_handler(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Event::FocusGained | Event::FocusLost | Event::Mouse(_)) => continue,
|
Ok(Event::FocusGained | Event::FocusLost | Event::Mouse(_)) => (),
|
||||||
Err(e) => break WatchEvent::TerminalEventErr(e),
|
Err(e) => break WatchEvent::TerminalEventErr(e),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
use std::{
|
use std::{
|
||||||
env::{self, consts::EXE_SUFFIX},
|
|
||||||
process::{Command, Stdio},
|
process::{Command, Stdio},
|
||||||
str::from_utf8,
|
str::from_utf8,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum Output<'a> {
|
enum Output<'a> {
|
||||||
FullStdout(&'a str),
|
|
||||||
PartialStdout(&'a str),
|
PartialStdout(&'a str),
|
||||||
PartialStderr(&'a str),
|
PartialStderr(&'a str),
|
||||||
}
|
}
|
||||||
@ -20,39 +18,24 @@ struct Cmd<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Cmd<'a> {
|
impl<'a> Cmd<'a> {
|
||||||
#[inline]
|
|
||||||
fn current_dir(&mut self, current_dir: &'a str) -> &mut Self {
|
fn current_dir(&mut self, current_dir: &'a str) -> &mut Self {
|
||||||
self.current_dir = Some(current_dir);
|
self.current_dir = Some(current_dir);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn args(&mut self, args: &'a [&'a str]) -> &mut Self {
|
fn args(&mut self, args: &'a [&'a str]) -> &mut Self {
|
||||||
self.args = args;
|
self.args = args;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn output(&mut self, output: Output<'a>) -> &mut Self {
|
fn output(&mut self, output: Output<'a>) -> &mut Self {
|
||||||
self.output = Some(output);
|
self.output = Some(output);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
fn assert(&self, success: bool) {
|
fn assert(&self, success: bool) {
|
||||||
let rustlings_bin = {
|
let mut cmd = Command::new(env!("CARGO_BIN_EXE_rustlings"));
|
||||||
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);
|
|
||||||
|
|
||||||
if let Some(current_dir) = self.current_dir {
|
if let Some(current_dir) = self.current_dir {
|
||||||
cmd.current_dir(current_dir);
|
cmd.current_dir(current_dir);
|
||||||
@ -60,38 +43,32 @@ impl<'a> Cmd<'a> {
|
|||||||
|
|
||||||
cmd.args(self.args).stdin(Stdio::null());
|
cmd.args(self.args).stdin(Stdio::null());
|
||||||
|
|
||||||
let status = match self.output {
|
let output = cmd.output().unwrap();
|
||||||
None => cmd
|
match self.output {
|
||||||
.stdout(Stdio::null())
|
None => (),
|
||||||
.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
|
|
||||||
}
|
|
||||||
Some(PartialStdout(stdout)) => {
|
Some(PartialStdout(stdout)) => {
|
||||||
let output = cmd.stderr(Stdio::null()).output().unwrap();
|
|
||||||
assert!(from_utf8(&output.stdout).unwrap().contains(stdout));
|
assert!(from_utf8(&output.stdout).unwrap().contains(stdout));
|
||||||
output.status
|
|
||||||
}
|
}
|
||||||
Some(PartialStderr(stderr)) => {
|
Some(PartialStderr(stderr)) => {
|
||||||
let output = cmd.stdout(Stdio::null()).output().unwrap();
|
|
||||||
assert!(from_utf8(&output.stderr).unwrap().contains(stderr));
|
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) {
|
fn success(&self) {
|
||||||
self.assert(true);
|
self.assert(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[track_caller]
|
||||||
fn fail(&self) {
|
fn fail(&self) {
|
||||||
self.assert(false);
|
self.assert(false);
|
||||||
}
|
}
|
||||||
@ -148,7 +125,7 @@ fn hint() {
|
|||||||
Cmd::default()
|
Cmd::default()
|
||||||
.current_dir("tests/test_exercises")
|
.current_dir("tests/test_exercises")
|
||||||
.args(&["hint", "test_failure"])
|
.args(&["hint", "test_failure"])
|
||||||
.output(FullStdout("The answer to everything: 42\n"))
|
.output(PartialStdout("\n\nHint:\nThe answer to everything: 42\n"))
|
||||||
.success();
|
.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,11 +6,11 @@ compile_sass = false
|
|||||||
build_search_index = false
|
build_search_index = false
|
||||||
|
|
||||||
[markdown]
|
[markdown]
|
||||||
highlight_code = true
|
|
||||||
highlight_theme = "dracula"
|
|
||||||
|
|
||||||
insert_anchor_links = "heading"
|
insert_anchor_links = "heading"
|
||||||
|
|
||||||
|
[markdown.highlighting]
|
||||||
|
theme = "dracula"
|
||||||
|
|
||||||
[extra]
|
[extra]
|
||||||
logo_path = "images/happy_ferris.svg"
|
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.
|
- 🇯🇵 [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.
|
- 🇨🇳 [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.
|
- 🇺🇦 [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.
|
> 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.
|
The default terminal on Linux and Mac should be sufficient.
|
||||||
On Windows, we recommend the [Windows Terminal](https://aka.ms/terminal).
|
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
|
## Usage
|
||||||
|
|
||||||
After being done with the setup, visit the [**usage**](@/usage/index.md) page for some info about using Rustlings 🚀
|
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;
|
@apply md:w-3/4 lg:w-3/5;
|
||||||
}
|
}
|
||||||
blockquote {
|
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 {
|
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;
|
@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;
|
||||||
}
|
|
||||||
pre code mark {
|
|
||||||
@apply pb-0.5 pt-1 pr-px text-inherit rounded-xs;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/cli": "^4.1"
|
"@tailwindcss/cli": "^4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user