diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 0317f351..a52d0730 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -5,11 +5,13 @@ on: branches: [main] paths-ignore: - website + - .github/workflows/website.yml - '*.md' pull_request: branches: [main] paths-ignore: - website + - .github/workflows/website.yml - '*.md' env: @@ -19,13 +21,13 @@ jobs: clippy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Clippy run: cargo clippy -- --deny warnings fmt: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: rustfmt run: cargo fmt --all --check test: @@ -34,14 +36,21 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macos-latest] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: swatinem/rust-cache@v2 - name: cargo test + env: + RUST_BACKTRACE: 1 run: cargo test --workspace dev-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: swatinem/rust-cache@v2 - name: rustlings dev check run: cargo dev check --require-solutions + rumdl: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: rvben/rumdl@v0 diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 936cd562..d437cf03 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -4,29 +4,36 @@ on: workflow_dispatch: push: branches: [main] - paths: [website] + paths: + - website + - .github/workflows/website.yml jobs: + rumdl: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: rvben/rumdl@v0 build: + needs: rumdl defaults: run: working-directory: website runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install TailwindCSS run: npm install - name: Build CSS run: npx @tailwindcss/cli -m -i input.css -o static/main.css - name: Download Zola - run: curl -fsSL https://github.com/getzola/zola/releases/download/v0.20.0/zola-v0.20.0-x86_64-unknown-linux-gnu.tar.gz | tar xz + run: curl -fsSL https://github.com/getzola/zola/releases/download/v0.22.1/zola-v0.22.1-x86_64-unknown-linux-gnu.tar.gz | tar xz - name: Build site run: ./zola build - name: Upload static files as artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v4 with: path: website/public/ - deploy: needs: build # Grant GITHUB_TOKEN the permissions required to make a Pages deployment @@ -40,4 +47,4 @@ jobs: runs-on: ubuntu-latest steps: - name: Deploy to GitHub Pages - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 diff --git a/.rumdl.toml b/.rumdl.toml new file mode 100644 index 00000000..528e0e31 --- /dev/null +++ b/.rumdl.toml @@ -0,0 +1,7 @@ +[global] +output-format = "full" +disable = ["MD013", "MD057"] + +[per-file-ignores] +"website/content/_index.md" = ["MD041"] +"website/content/**/*.md" = ["MD028", "MD033"] diff --git a/CHANGELOG.md b/CHANGELOG.md index 18e0aa66..c15818a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ +# Changelog + ## Unreleased +### Added + +- Automatically open the current file if Rustlings is running in a VS Code terminal +- Automatically open the current file with `$EDITOR` in a new pane if Rustlings is running in [Zellij](https://zellij.dev) +- New argument `--no-editor` to disable automatic opening of the current file in VS Code or Zellij +- New argument `--edit-cmd` to communicate with an editor running in a different process to open the current exercise +- Show the file link of the current exercise when running `rustlings hint` and `rustlings reset` + +### Fixed + +- Fix integer overflow on big terminal widths [@gabfec](https://github.com/gabfec) +- Fix workspace detection on Windows [@senekor](https://github.com/senekor) + +### Changed + +- Avoid initializing a nested Git repository [@senekor](https://github.com/senekor) +- `vecs2`: Removed the use of `map` and `collect`, which are only taught later. +- `structs3`: Rewrote the exercise to make users type method syntax themselves. + ## 6.5.0 (2025-08-21) ### Added @@ -105,17 +126,17 @@ ## 6.1.0 (2024-07-10) -#### Added +### Added - `dev check`: Check that all exercises (including community ones) include at least one `TODO` comment. - `dev check`: Check that all exercises actually fail to run (not already solved). -#### Changed +### Changed - Make enum variants more consistent between enum exercises. - `iterators3`: Teach about the possible case of integer overflow during division. -#### Fixed +### Fixed - Exit with a helpful error message on missing/unsupported terminal/TTY. - Mark the last exercise as done. @@ -192,11 +213,11 @@ Then follow the link to the guide about [community exercises](https://rustlings. ## 5.6.1 (2023-09-18) -#### Changed +### Changed - Converted all exercises with assertions to test mode. -#### Fixed +### Fixed - `cow1`: Reverted regression introduced by calling `to_mut` where it shouldn't have been called, and clarified comment. @@ -207,7 +228,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. ## 5.6.0 (2023-09-04) -#### Added +### Added - New exercise: `if3`, teaching the user about `if let` statements. - `hashmaps2`: Added an extra test function to check if the amount of fruits is higher than zero. @@ -215,7 +236,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. - `if1`: Added a test case to check equal values. - `if3`: Added a note specifying that there are no test changes needed. -#### Changed +### Changed - Swapped the order of threads and smart pointer exercises. - Rewrote the CLI to use `clap` - it's matured much since we switched to `argh` :) @@ -223,7 +244,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. - `move_semantics`: Switched 1-4 to tests, and rewrote them to be way simpler, while still teaching about the same concepts. -#### Fixed +### Fixed - `iterators5`: - Removed an outdated part of the hint. @@ -238,7 +259,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. - `cow1`: Added `.to_mut()` to distinguish from the previous test case. - `threads2`: Updated hint text to reference the correct book heading. -#### Housekeeping +### Housekeeping - Cleaned up the explanation paragraphs at the start of each exercise. - Lots of Nix housekeeping that I don't feel qualified to write about! @@ -246,13 +267,13 @@ Then follow the link to the guide about [community exercises](https://rustlings. ## 5.5.1 (2023-05-17) -#### Fixed +### Fixed - Reverted `rust-project.json` path generation due to an upstream `rust-analyzer` fix. ## 5.5.0 (2023-05-17) -#### Added +### Added - `strings2`: Added a reference to the book chapter for reference conversion - `lifetimes`: Added a link to the lifetimekata project @@ -260,7 +281,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. - Added a `!` prefix command to watch mode that runs an external command - Added a `--success-hints` option to watch mode that shows hints on exercise success -#### Changed +### Changed - `vecs2`: Renamed iterator variable bindings for clarify - `lifetimes`: Changed order of book references @@ -269,7 +290,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. - `options2`: Improved tests for layering options - `modules2`: Added more information to the hint -#### Fixed +### Fixed - `errors2`: Corrected a comment wording - `iterators2`: Fixed a spelling mistake in the hint text @@ -279,20 +300,20 @@ Then follow the link to the guide about [community exercises](https://rustlings. - `options3`: Changed exercise to panic when no match is found - `rustlings lsp` now generates absolute paths, which should fix VSCode `rust-analyzer` usage on Windows -#### Housekeeping +### Housekeeping - Added a markdown linter to run on GitHub actions - Split quick installation section into two code blocks ## 5.4.1 (2023-03-10) -#### Changed +### Changed - `vecs`: Added links to `iter_mut` and `map` to README.md - `cow1`: Changed main to tests - `iterators1`: Formatted according to rustfmt -#### Fixed +### Fixed - `errors5`: Unified undisclosed type notation - `arc1`: Improved readability by avoiding implicit dereference @@ -301,7 +322,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. ## 5.4.0 (2023-02-12) -#### Changed +### Changed - Reordered exercises - Unwrapped `standard_library_types` into `iterators` and `smart_pointers` @@ -313,7 +334,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. - Made progress bar update proportional to amount of files verified - Decreased `watch` delay from 2 to 1 second -#### Fixed +### Fixed - Capitalized "Rust" in exercise hints - **enums3**: Removed superfluous tuple brackets @@ -323,23 +344,23 @@ Then follow the link to the guide about [community exercises](https://rustlings. - Fixed a typo in a method name - Specified the edition in `rustc` commands -#### Housekeeping +### Housekeeping - Bumped min Rust version to 1.58 in installation script ## 5.3.0 (2022-12-23) -#### Added +### Added - **cli**: Added a percentage display in watch mode - Added a `flake.nix` for Nix users -#### Changed +### Changed - **structs3**: Added an additional test - **macros**: Added a link to MacroKata in the README -#### Fixed +### Fixed - **strings3**: Added a link to `std` in the hint - **threads1**: Corrected a hint link @@ -353,7 +374,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. - **enums2**: Removed unnecessary indirection of self - **enums3**: Added an extra tuple comment -#### Housekeeping +### Housekeeping - Added a VSCode extension recommendation - Applied some Clippy and rustfmt formatting @@ -361,28 +382,28 @@ Then follow the link to the guide about [community exercises](https://rustlings. ## 5.2.1 (2022-09-06) -#### Fixed +### Fixed - **quiz1**: Reworded the comment to actually reflect what's going on in the tests. Also added another assert just to make sure. - **rc1**: Fixed a typo in the hint. - **lifetimes**: Add quotes to the `println!` output, for readability. -#### Housekeeping +### Housekeeping - Fixed a typo in README.md ## 5.2.0 (2022-08-27) -#### Added +### Added - Added a `reset` command -#### Changed +### Changed - **options2**: Convert the exercise to use tests -#### Fixed +### Fixed - **threads3**: Fixed a typo - **quiz1**: Adjusted the explanations to be consistent with @@ -390,18 +411,18 @@ Then follow the link to the guide about [community exercises](https://rustlings. ## 5.1.1 (2022-08-17) -#### Bug Fixes +### Bug Fixes - Fixed an incorrect assertion in options1 ## 5.1.0 (2022-08-16) -#### Features +### Features - Added a new `rc1` exercise. - Added a new `cow1` exercise. -#### Bug Fixes +### Bug Fixes - **variables5**: Corrected reference to previous exercise - **functions4**: Fixed line number reference @@ -421,7 +442,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. - Added more granular tests - Fixed some comment syntax shenanigans in info.toml -#### Housekeeping +### Housekeeping - Fixed a typo in .editorconfig - Fixed a typo in integration_tests.rs @@ -430,7 +451,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. ## 5.0.0 (2022-07-16) -#### Features +### Features - Hint comments in exercises now also include a reference to the `hint` watch mode subcommand. @@ -462,7 +483,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. - Added 3 new lifetimes exercises. - Added 3 new traits exercises. -#### Bug Fixes +### Bug Fixes - **variables2**: Made output messages more verbose. - **variables5**: Added a nudging hint about shadowing. @@ -486,7 +507,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. `Box`. - **try_from_into**: Fixed the function name in comment. -#### Removed +### Removed - Removed the legacy LSP feature that was using `mod.rs` files. - Removed `quiz4`. @@ -494,7 +515,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. order, and I've always felt like they didn't quite fit in with the mostly simple, book-following style we've had in Rustlings. -#### Housekeeping +### Housekeeping - Added missing exercises to the book index. - Updated spacing in Cargo.toml. @@ -502,53 +523,53 @@ Then follow the link to the guide about [community exercises](https://rustlings. ## 4.8.0 (2022-07-01) -#### Features +### Features - Added a progress indicator for `rustlings watch`. - The installation script now checks for Rustup being installed. - Added a `rustlings lsp` command to enable `rust-analyzer`. -#### Bug Fixes +### Bug Fixes - **move_semantics5**: Replaced "in vogue" with "in scope" in hint. - **if2**: Fixed a typo in the hint. - **variables1**: Fixed an incorrect line reference in the hint. - Fixed an out of bounds check in the installation Bash script. -#### Housekeeping +### Housekeeping - Replaced the git.io URL with the fully qualified URL because of git.io's sunsetting. - Removed the deprecated Rust GitPod extension. ## 4.7.1 (2022-04-20) -#### Features +### Features - The amount of dependency crates that need to be compiled went down from ~65 to ~45 by bumping dependency versions. - The minimum Rust version in the install scripts has been bumped to 1.56.0 (this isn't in the release itself, since install scripts don't really get versioned) -#### Bug Fixes +### Bug Fixes - **arc1**: A small part has been rewritten using a more functional code style (#968). - **using_as**: A small part has been refactored to use `sum` instead of `fold`, resulting in better readability. -#### Housekeeping +### Housekeeping - The changelog will now be manually written instead of being automatically generated by the Git log. ## 4.7.0 (2022-04-14) -#### Features +### Features - Add move_semantics6.rs exercise (#908) ([3f0e1303](https://github.com/rust-lang/rustlings/commit/3f0e1303e0b3bf3fecc0baced3c8b8a37f83c184)) - **intro:** Add intro section. ([21c9f441](https://github.com/rust-lang/rustlings/commit/21c9f44168394e08338fd470b5f49b1fd235986f)) - Include exercises folder in the project structure behind a feature, enabling rust-analyzer to work (#917) ([179a75a6](https://github.com/rust-lang/rustlings/commit/179a75a68d03ac9518dec2297fb17f91a4fc506b)) -#### Bug Fixes +### Bug Fixes - Fix a few spelling mistakes ([1c0fe3cb](https://github.com/rust-lang/rustlings/commit/1c0fe3cbcca85f90b3985985b8e265ee872a2ab2)) - **cli:** @@ -575,14 +596,14 @@ Then follow the link to the guide about [community exercises](https://rustlings. - **structs3.rs:** assigned value to cents_per_gram in test ([d1ee2daf](https://github.com/rust-lang/rustlings/commit/d1ee2daf14f19105e6db3f9c610f44293d688532)) - **traits1:** rename test functions to snake case (#854) ([1663a16e](https://github.com/rust-lang/rustlings/commit/1663a16eade6ca646b6ed061735f7982434d530d)) -#### Documentation improvements +### Documentation improvements - Add hints on how to get GCC installed (#741) ([bc56861](https://github.com/rust-lang/rustlings/commit/bc5686174463ad6f4f6b824b0e9b97c3039d4886)) - Fix some code blocks that were not highlighted ([17f9d74](https://github.com/rust-lang/rustlings/commit/17f9d7429ccd133a72e815fb5618e0ce79560929)) ## 4.6.0 (2021-09-25) -#### Features +### Features - add advanced_errs2 ([abd6b70c](https://github.com/rust-lang/rustlings/commit/abd6b70c72dc6426752ff41f09160b839e5c449e)) - add advanced_errs1 ([882d535b](https://github.com/rust-lang/rustlings/commit/882d535ba8628d5e0b37e8664b3e2f26260b2671)) @@ -591,7 +612,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. - **modules:** update exercises, add modules3 (#822) ([dfd2fab4](https://github.com/rust-lang/rustlings/commit/dfd2fab4f33d1bf59e2e5ee03123c0c9a67a9481)) - **quiz1:** add default function name in comment (#838) ([0a11bad7](https://github.com/rust-lang/rustlings/commit/0a11bad71402b5403143d642f439f57931278c07)) -#### Bug Fixes +### Bug Fixes - Correct small typo in exercises/conversions/from_str.rs ([86cc8529](https://github.com/rust-lang/rustlings/commit/86cc85295ae36948963ae52882e285d7e3e29323)) - **cli:** typo in exercise.rs (#848) ([06d5c097](https://github.com/rust-lang/rustlings/commit/06d5c0973a3dffa3c6c6f70acb775d4c6630323c)) @@ -604,12 +625,12 @@ Then follow the link to the guide about [community exercises](https://rustlings. ## 4.5.0 (2021-07-07) -#### Features +### Features - Add move_semantics5 exercise. (#746) ([399ab328](https://github.com/rust-lang/rustlings/commit/399ab328d8d407265c09563aa4ef4534b2503ff2)) - **cli:** Add "next" to run the next unsolved exercise. (#785) ([d20e413a](https://github.com/rust-lang/rustlings/commit/d20e413a68772cd493561f2651cf244e822b7ca5)) -#### Bug Fixes +### Bug Fixes - rename result1 to errors4 ([50ab289d](https://github.com/rust-lang/rustlings/commit/50ab289da6b9eb19a7486c341b00048c516b88c0)) - move_semantics5 hints ([1b858285](https://github.com/rust-lang/rustlings/commit/1b85828548f46f58b622b5e0c00f8c989f928807)) @@ -624,7 +645,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. ## 4.4.0 (2021-04-24) -#### Bug Fixes +### Bug Fixes - Fix spelling error in main.rs ([91ee27f2](https://github.com/rust-lang/rustlings/commit/91ee27f22bd3797a9db57e5fd430801c170c5db8)) - typo in default out text ([644c49f1](https://github.com/rust-lang/rustlings/commit/644c49f1e04cbb24e95872b3a52b07d692ae3bc8)) @@ -652,7 +673,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. - **threads1:** line number correction ([7857b0a6](https://github.com/rust-lang/rustlings/commit/7857b0a689b0847f48d8c14cbd1865e3b812d5ca)) - **try_from_into:** use trait objects ([2e93a588](https://github.com/rust-lang/rustlings/commit/2e93a588e0abe8badb7eafafb9e7d073c2be5df8)) -#### Features +### Features - Replace clap with argh ([7928122f](https://github.com/rust-lang/rustlings/commit/7928122fcef9ca7834d988b1ec8ca0687478beeb)) - Replace emojis when NO_EMOJI env variable present ([8d62a996](https://github.com/rust-lang/rustlings/commit/8d62a9963708dbecd9312e8bcc4b47049c72d155)) @@ -665,7 +686,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. ## 4.3.0 (2020-12-29) -#### Features +### Features - Rewrite default out text ([44d39112](https://github.com/rust-lang/rustlings/commit/44d39112ff122b29c9793fe52e605df1612c6490)) - match exercise order to book chapters (#541) ([033bf119](https://github.com/rust-lang/rustlings/commit/033bf1198fc8bfce1b570e49da7cde010aa552e3)) @@ -673,7 +694,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. - add "rustlings list" command ([838f9f30](https://github.com/rust-lang/rustlings/commit/838f9f30083d0b23fd67503dcf0fbeca498e6647)) - **try_from_into:** remove duplicate annotation ([04f1d079](https://github.com/rust-lang/rustlings/commit/04f1d079aa42a2f49af694bc92c67d731d31a53f)) -#### Bug Fixes +### Bug Fixes - update structs README ([bcf14cf6](https://github.com/rust-lang/rustlings/commit/bcf14cf677adb3a38a3ac3ca53f3c69f61153025)) - added missing exercises to info.toml ([90cfb6ff](https://github.com/rust-lang/rustlings/commit/90cfb6ff28377531bfc34acb70547bdb13374f6b)) @@ -687,14 +708,14 @@ Then follow the link to the guide about [community exercises](https://rustlings. ## 4.2.0 (2020-11-07) -#### Features +### Features - Add HashMap exercises ([633c00cf](https://github.com/rust-lang/rustlings/commit/633c00cf8071e1e82959a3010452a32f34f29fc9)) - Add Vec exercises ([0c12fa31](https://github.com/rust-lang/rustlings/commit/0c12fa31c57c03c6287458a0a8aca7afd057baf6)) - **primitive_types6:** Add a test (#548) ([2b1fb2b7](https://github.com/rust-lang/rustlings/commit/2b1fb2b739bf9ad8d6b7b12af25fee173011bfc4)) - **try_from_into:** Add tests (#571) ([95ccd926](https://github.com/rust-lang/rustlings/commit/95ccd92616ae79ba287cce221101e0bbe4f68cdc)) -#### Bug Fixes +### Bug Fixes - log error output when inotify limit is exceeded ([d61b4e5a](https://github.com/rust-lang/rustlings/commit/d61b4e5a13b44d72d004082f523fa1b6b24c1aca)) - more unique temp_file ([5643ef05](https://github.com/rust-lang/rustlings/commit/5643ef05bc81e4a840e9456f4406a769abbe1392)) @@ -707,7 +728,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. ## 4.1.0 (2020-10-05) -#### Bug Fixes +### Bug Fixes - Update rustlings version in Cargo.lock ([1cc40bc9](https://github.com/rust-lang/rustlings/commit/1cc40bc9ce95c23d56f6d91fa1c4deb646231fef)) - **arc1:** index mod should equal thread count ([b4062ef6](https://github.com/rust-lang/rustlings/commit/b4062ef6993e80dac107c4093ea85166ad3ee0fa)) @@ -717,7 +738,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. - **structs3:** Small adjustment of variable name ([114b54cb](https://github.com/rust-lang/rustlings/commit/114b54cbdb977234b39e5f180d937c14c78bb8b2)) - **using_as:** Add test so that proper type is returned. (#512) ([3286c5ec](https://github.com/rust-lang/rustlings/commit/3286c5ec19ea5fb7ded81d047da5f8594108a490)) -#### Features +### Features - Added iterators1.rs exercise ([9642f5a3](https://github.com/rust-lang/rustlings/commit/9642f5a3f686270a4f8f6ba969919ddbbc4f7fdd)) - Add ability to run rustlings on repl.it (#471) ([8f7b5bd0](https://github.com/rust-lang/rustlings/commit/8f7b5bd00eb83542b959830ef55192d2d76db90a)) @@ -729,12 +750,12 @@ Then follow the link to the guide about [community exercises](https://rustlings. ## 4.0.0 (2020-07-08) -#### Breaking Changes +### Breaking Changes - Add a --nocapture option to display test harnesses' outputs ([8ad5f9bf](https://github.com/rust-lang/rustlings/commit/8ad5f9bf531a4848b1104b7b389a20171624c82f)) - Rename test to quiz, fixes #244 ([010a0456](https://github.com/rust-lang/rustlings/commit/010a04569282149cea7f7a76fc4d7f4c9f0f08dd)) -#### Features +### Features - Add traits README ([173bb141](https://github.com/rust-lang/rustlings/commit/173bb14140c5530cbdb59e53ace3991a99d804af)) - Add box1.rs exercise ([7479a473](https://github.com/rust-lang/rustlings/commit/7479a4737bdcac347322ad0883ca528c8675e720)) @@ -743,7 +764,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. - Added exercise structs3.rs ([b66e2e09](https://github.com/rust-lang/rustlings/commit/b66e2e09622243e086a0f1258dd27e1a2d61c891)) - Add exercise variables6 covering const (#352) ([5999acd2](https://github.com/rust-lang/rustlings/commit/5999acd24a4f203292be36e0fd18d385887ec481)) -#### Bug Fixes +### Bug Fixes - Change then to than ([ddd98ad7](https://github.com/rust-lang/rustlings/commit/ddd98ad75d3668fbb10eff74374148aa5ed2344d)) - rename quiz1 to tests1 in info (#420) ([0dd1c6ca](https://github.com/rust-lang/rustlings/commit/0dd1c6ca6b389789e0972aa955fe17aa15c95f29)) @@ -770,11 +791,11 @@ Then follow the link to the guide about [community exercises](https://rustlings. ## 3.0.0 (2020-04-11) -#### Breaking Changes +### Breaking Changes - make "compile" exercises print output (#278) ([3b6d5c](https://github.com/fmoko/rustlings/commit/3b6d5c3aaa27a242a832799eb66e96897d26fde3)) -#### Bug Fixes +### Bug Fixes - **primitive_types:** revert primitive_types4 (#296) ([b3a3351e](https://github.com/rust-lang/rustlings/commit/b3a3351e8e6a0bdee07077d7b0382953821649ae)) - **run:** compile clippy exercise files (#295) ([3ab084a4](https://github.com/rust-lang/rustlings/commit/3ab084a421c0f140ae83bf1fc3f47b39342e7373)) @@ -783,26 +804,26 @@ Then follow the link to the guide about [community exercises](https://rustlings. - remove duplicate not done comment (#292) ([dab90f](https://github.com/fmoko/rustlings/commit/dab90f7b91a6000fe874e3d664f244048e5fa342)) - don't hardcode documentation version for traits (#288) ([30e6af](https://github.com/fmoko/rustlings/commit/30e6af60690c326fb5d3a9b7335f35c69c09137d)) -#### Features +### Features - add Option2 exercise (#290) ([86b5c08b](https://github.com/rust-lang/rustlings/commit/86b5c08b9bea1576127a7c5f599f5752072c087d)) - add exercise for option (#282) ([135e5d47](https://github.com/rust-lang/rustlings/commit/135e5d47a7c395aece6f6022117fb20c82f2d3d4)) - add new exercises for generics (#280) ([76be5e4e](https://github.com/rust-lang/rustlings/commit/76be5e4e991160f5fd9093f03ee2ba260e8f7229)) - **ci:** add buildkite config ([b049fa2c](https://github.com/rust-lang/rustlings/commit/b049fa2c84dba0f0c8906ac44e28fd45fba51a71)) -### 2.2.1 (2020-02-27) +## 2.2.1 (2020-02-27) -#### Bug Fixes +### Bug Fixes - Re-add cloning the repo to install scripts ([3d9b03c5](https://github.com/rust-lang/rustlings/commit/3d9b03c52b8dc51b140757f6fd25ad87b5782ef5)) -#### Features +### Features - Add clippy lints (#269) ([1e2fd9c9](https://github.com/rust-lang/rustlings/commit/1e2fd9c92f8cd6e389525ca1a999fca4c90b5921)) ## 2.2.0 (2020-02-25) -#### Bug Fixes +### Bug Fixes - Update deps to version compatible with aarch64-pc-windows (#263) ([19a93428](https://github.com/rust-lang/rustlings/commit/19a93428b3c73d994292671f829bdc8e5b7b3401)) - **docs:** @@ -817,7 +838,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. - Change test command ([fe10e06c](https://github.com/rust-lang/rustlings/commit/fe10e06c3733ddb4a21e90d09bf79bfe618e97ce) - Correct test command in tests1.rs comment (#263) ([39fa7ae](https://github.com/rust-lang/rustlings/commit/39fa7ae8b70ad468da49b06f11b2383135a63bcf)) -#### Features +### Features - Add variables5.rs exercise (#264) ([0c73609e](https://github.com/rust-lang/rustlings/commit/0c73609e6f2311295e95d6f96f8c747cfc4cba03)) - Show a completion message when watching (#253) ([d25ee55a](https://github.com/rust-lang/rustlings/commit/d25ee55a3205882d35782e370af855051b39c58c)) @@ -829,7 +850,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. ## 2.1.0 (2019-11-27) -#### Bug Fixes +### Bug Fixes - add line numbers in several exercises and hints ([b565c4d3](https://github.com/rust-lang/rustlings/commit/b565c4d3e74e8e110bef201a082fa1302722a7c3)) - **arc1:** Fix some words in the comment ([c42c3b21](https://github.com/rust-lang/rustlings/commit/c42c3b2101df9164c8cd7bb344def921e5ba3e61)) @@ -840,33 +861,33 @@ Then follow the link to the guide about [community exercises](https://rustlings. - **strings2:** update line number in hint ([a09f684f](https://github.com/rust-lang/rustlings/commit/a09f684f05c58d239a6fc59ec5f81c2533e8b820)) - **variables1:** Correct wrong word in comment ([fda5a470](https://github.com/rust-lang/rustlings/commit/fda5a47069e0954f16a04e8e50945e03becb71a5)) -#### Features +### Features - **watch:** show hint while watching ([8143d57b](https://github.com/rust-lang/rustlings/commit/8143d57b4e88c51341dd4a18a14c536042cc009c)) ## 2.0.0 (2019-11-12) -#### Bug Fixes +### Bug Fixes - **default:** Clarify the installation procedure ([c371b853](https://github.com/rust-lang/rustlings/commit/c371b853afa08947ddeebec0edd074b171eeaae0)) - **info:** Fix trailing newlines for hints ([795b6e34](https://github.com/rust-lang/rustlings/commit/795b6e348094a898e9227a14f6232f7bb94c8d31)) - **run:** make `run` never prompt ([4b265465](https://github.com/rust-lang/rustlings/commit/4b26546589f7d2b50455429482cf1f386ceae8b3)) -#### Breaking Changes +### Breaking Changes - Refactor hint system ([9bdb0a12](https://github.com/rust-lang/rustlings/commit/9bdb0a12e45a8e9f9f6a4bd4a9c172c5376c7f60)) - improve `watch` execution mode ([2cdd6129](https://github.com/rust-lang/rustlings/commit/2cdd61294f0d9a53775ee24ad76435bec8a21e60)) - Index exercises by name ([627cdc07](https://github.com/rust-lang/rustlings/commit/627cdc07d07dfe6a740e885e0ddf6900e7ec336b)) - **run:** makes `run` never prompt ([4b265465](https://github.com/rust-lang/rustlings/commit/4b26546589f7d2b50455429482cf1f386ceae8b3)) -#### Features +### Features - **cli:** check for rustc before doing anything ([36a033b8](https://github.com/rust-lang/rustlings/commit/36a033b87a6549c1e5639c908bf7381c84f4f425)) - **hint:** Add test for hint ([ce9fa6eb](https://github.com/rust-lang/rustlings/commit/ce9fa6ebbfdc3e7585d488d9409797285708316f)) -### 1.5.1 (2019-11-11) +## 1.5.1 (2019-11-11) -#### Bug Fixes +### Bug Fixes - **errors3:** Update hint ([dcfb427b](https://github.com/rust-lang/rustlings/commit/dcfb427b09585f0193f0a294443fdf99f11c64cb), closes [#185](https://github.com/rust-lang/rustlings/issues/185)) - **if1:** Remove `return` reference ([ad03d180](https://github.com/rust-lang/rustlings/commit/ad03d180c9311c0093e56a3531eec1a9a70cdb45)) @@ -877,7 +898,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. ## 1.5.0 (2019-11-09) -#### Bug Fixes +### Bug Fixes - **test1:** Rewrite logic ([79a56942](https://github.com/rust-lang/rustlings/commit/79a569422c8309cfc9e4aed25bf4ab3b3859996b)) - **installation:** Fix rustlings installation check ([7a252c47](https://github.com/rust-lang/rustlings/commit/7a252c475551486efb52f949b8af55803b700bc6)) @@ -893,15 +914,15 @@ Then follow the link to the guide about [community exercises](https://rustlings. - Swap assertion parameter order ([4086d463](https://github.com/rust-lang/rustlings/commit/4086d463a981e81d97781851d17db2ced290f446)) - renamed function name to snake case closes #180 ([89d5186c](https://github.com/rust-lang/rustlings/commit/89d5186c0dae8135ecabf90ee8bb35949bc2d29b)) -#### Features +### Features - Add enums exercises ([dc150321](https://github.com/rust-lang/rustlings/commit/dc15032112fc485226a573a18139e5ce928b1755)) - Added exercise for struct update syntax ([1c4c8764](https://github.com/rust-lang/rustlings/commit/1c4c8764ed118740cd4cee73272ddc6cceb9d959)) - **iterators2:** adds iterators2 exercise including config ([9288fccf](https://github.com/rust-lang/rustlings/commit/9288fccf07a2c5043b76d0fd6491e4cf72d76031)) -### 1.4.1 (2019-08-13) +## 1.4.1 (2019-08-13) -#### Bug Fixes +### Bug Fixes - **iterators2:** Remove syntax resulting in misleading error message ([4cde8664](https://github.com/rust-lang/rustlings/commit/4cde86643e12db162a66e62f23b78962986046ac)) - **option1:** Add test for prematurely passing exercise ([a750e4a1](https://github.com/rust-lang/rustlings/commit/a750e4a1a3006227292bb17d57d78ce84da6bfc6)) @@ -909,7 +930,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. ## 1.4.0 (2019-07-13) -#### Bug Fixes +### Bug Fixes - **installation:** Fix rustlings installation check ([7a252c47](https://github.com/rust-lang/rustlings/commit/7a252c475551486efb52f949b8af55803b700bc6)) - **iterators:** Rename iterator3.rs ([433d2115](https://github.com/rust-lang/rustlings/commit/433d2115bc1c04b6d34a335a18c9a8f3e2672bc6)) @@ -918,18 +939,18 @@ Then follow the link to the guide about [community exercises](https://rustlings. - **cli:** Check if changed exercise file exists before calling verify ([ba85ca3](https://github.com/rust-lang/rustlings/commit/ba85ca32c4cfc61de46851ab89f9c58a28f33c88)) - **structs1:** Fix the irrefutable let pattern warning ([cc6a141](https://github.com/rust-lang/rustlings/commit/cc6a14104d7c034eadc98297eaaa972d09c50b1f)) -#### Features +### Features - **changelog:** Use clog for changelogs ([34e31232](https://github.com/rust-lang/rustlings/commit/34e31232dfddde284a341c9609b33cd27d9d5724)) - **iterators2:** adds iterators2 exercise including config ([9288fccf](https://github.com/rust-lang/rustlings/commit/9288fccf07a2c5043b76d0fd6491e4cf72d76031)) -### 1.3.0 (2019-06-05) +## 1.3.0 (2019-06-05) -#### Features +### Features - Adds a simple exercise for structures (#163, @briankung) -#### Bug Fixes +### Bug Fixes - Add Result type signature as it is difficult for new comers to understand Generics and Error all at once. (#157, @veggiemonk) - Rustfmt and whitespace fixes (#161, @eddyp) @@ -938,29 +959,29 @@ Then follow the link to the guide about [community exercises](https://rustlings. - Fix broken link (#164, @HanKruiger) - Remove highlighting and syntect (#167, @komaeda) -### 1.2.2 (2019-05-07) +## 1.2.2 (2019-05-07) -#### Bug Fixes +### Bug Fixes - Reverted `--nocapture` flag since it was causing tests to pass unconditionally -### 1.2.1 (2019-04-22) +## 1.2.1 (2019-04-22) -#### Bug Fixes +### Bug Fixes - Fix the `--nocapture` feature (@komaeda) - Provide a nicer error message for when you're in the wrong directory -### 1.2.0 (2019-04-22) +## 1.2.0 (2019-04-22) -#### Features +### Features - Add errors to exercises that compile without user changes (@yvan-sraka) - Use --nocapture when testing, enabling `println!` when running (@komaeda) -### 1.1.1 (2019-04-14) +## 1.1.1 (2019-04-14) -#### Bug fixes +### Bug fixes - Fix permissions on exercise files (@zacanger, #133) - Make installation checks more thorough (@komaeda, 1b3469f236bc6979c27f6e1a04e4138a88e55de3) @@ -970,7 +991,7 @@ Then follow the link to the guide about [community exercises](https://rustlings. - Fix links by deleting book version (@diodfr, #142) - Canonicalize paths to fix path matching (@cjpearce, #143) -### 1.1.0 (2019-03-20) +## 1.1.0 (2019-03-20) - errors2.rs: update link to Rust book (#124) - Start verification at most recently modified file (#120) @@ -979,12 +1000,12 @@ Then follow the link to the guide about [community exercises](https://rustlings. - Give a warning when Rustlings isn't run from the right directory (#123) - Verify that rust version is recent enough to install Rustlings (#131) -### 1.0.1 (2019-03-06) +## 1.0.1 (2019-03-06) - Adds a way to install Rustlings in one command (`curl -L https://git.io/rustlings | bash`) - Makes `rustlings watch` react to create file events (@shaunbennett, #117) - Reworks the exercise management to use an external TOML file instead of just listing them in the code -### 1.0.0 (2019-03-06) +## 1.0.0 (2019-03-06) Initial release. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 95605f70..7b684d39 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ I want to … ## Issues -You can open an issue [here](https://github.com/rust-lang/rustlings/issues/new). +You can [open an issue](https://github.com/rust-lang/rustlings/issues/new). If you're reporting a bug, please include the output of the following commands: - `cargo --version` diff --git a/Cargo.lock b/Cargo.lock index f883653f..b8db4360 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "anstream" -version = "0.6.20" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -19,50 +19,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "bitflags" @@ -72,21 +66,21 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.2" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.45" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -94,9 +88,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.44" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -106,9 +100,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.45" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", @@ -118,15 +112,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "crossterm" @@ -134,7 +128,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.11.0", "crossterm_winapi", "document-features", "mio", @@ -156,9 +150,9 @@ dependencies = [ [[package]] name = "document-features" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ "litrs", ] @@ -171,19 +165,25 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "a043dc74da1e37d6afe657061213aa6f425f855399a11d3463c6ecccc4dfda1f" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "fsevent-sys" @@ -196,14 +196,15 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.3" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasip2", + "wasip3", ] [[package]] @@ -211,6 +212,15 @@ name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" @@ -219,22 +229,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "indexmap" -version = "2.10.0" +name = "id-arena" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] name = "inotify" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.11.0", "inotify-sys", "libc", ] @@ -250,15 +268,15 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "kqueue" @@ -281,55 +299,60 @@ dependencies = [ ] [[package]] -name = "libc" -version = "0.2.175" +name = "leb128fmt" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.184" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litrs" -version = "0.4.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.27" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" -version = "2.7.5" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "mio" -version = "1.0.4" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "log", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] @@ -338,7 +361,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.11.0", "fsevent-sys", "inotify", "kqueue", @@ -352,27 +375,30 @@ dependencies = [ [[package]] name = "notify-types" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags 2.11.0", +] [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -380,61 +406,71 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", ] [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" -version = "5.3.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.11.0", ] [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -449,6 +485,7 @@ dependencies = [ "rustlings-macros", "serde", "serde_json", + "shlex", "tempfile", "toml", ] @@ -462,12 +499,6 @@ dependencies = [ "toml", ] -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - [[package]] name = "same-file" version = "1.0.6" @@ -484,19 +515,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "serde" -version = "1.0.219" +name = "semver" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -505,25 +552,32 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.143" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] name = "serde_spanned" -version = "1.0.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ - "serde", + "serde_core", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook" version = "0.3.18" @@ -536,9 +590,9 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" dependencies = [ "libc", "mio", @@ -547,10 +601,11 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -568,9 +623,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -579,25 +634,25 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.21.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", "getrandom", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "toml" -version = "0.9.5" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ "indexmap", - "serde", + "serde_core", "serde_spanned", "toml_datetime", "toml_parser", @@ -607,33 +662,39 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ - "serde", + "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.2" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.2" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utf8parse" @@ -658,12 +719,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", ] [[package]] @@ -684,11 +788,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -699,18 +803,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-link" -version = "0.1.3" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" @@ -718,149 +813,179 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets", ] [[package]] -name = "windows-targets" -version = "0.52.6" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.12" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" dependencies = [ - "bitflags 2.9.2", + "wit-bindgen-rust-macro", ] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 4469b281..192eeb61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,8 +18,8 @@ edition = "2024" # On Update: Update the edition of `rustfmt` in `dev check` and rust-version = "1.88" [workspace.dependencies] -serde = { version = "1.0", features = ["derive"] } -toml = { version = "0.9", default-features = false, features = ["std", "parse", "serde"] } +serde = { version = "1", features = ["derive"] } +toml = { version = "1", default-features = false, features = ["std", "parse", "serde"] } [package] name = "rustlings" @@ -45,20 +45,21 @@ include = [ ] [dependencies] -anyhow = "1.0" -clap = { version = "4.5", features = ["derive"] } +anyhow = "1" +clap = { version = "4", features = ["derive"] } crossterm = { version = "0.29", default-features = false, features = ["windows", "events"] } -notify = "8.0" +notify = "8" rustlings-macros = { path = "rustlings-macros", version = "=6.5.0" } -serde_json = "1.0" +serde_json = "1" serde.workspace = true +shlex = "1" toml.workspace = true [target.'cfg(not(windows))'.dependencies] rustix = { version = "1.0", default-features = false, features = ["std", "stdio", "termios"] } [dev-dependencies] -tempfile = "3.21" +tempfile = "3" [profile.release] panic = "abort" diff --git a/exercises/05_vecs/vecs2.rs b/exercises/05_vecs/vecs2.rs index a9be2580..0c996266 100644 --- a/exercises/05_vecs/vecs2.rs +++ b/exercises/05_vecs/vecs2.rs @@ -9,26 +9,6 @@ fn vec_loop(input: &[i32]) -> Vec { output } -fn vec_map_example(input: &[i32]) -> Vec { - // 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 { - // TODO: Here, we also want to multiply each element in the `input` slice - // by 2, but with iterator mapping instead of manually pushing into an empty - // vector. - // See the example in the function `vec_map_example` above. - input - .iter() - .map(|element| { - // ??? - }) - .collect() -} - fn main() { // You can optionally experiment here. } @@ -43,18 +23,4 @@ mod tests { let ans = vec_loop(&input); assert_eq!(ans, [4, 8, 12, 16, 20]); } - - #[test] - fn test_vec_map_example() { - let input = [1, 2, 3]; - let ans = vec_map_example(&input); - assert_eq!(ans, [2, 3, 4]); - } - - #[test] - fn test_vec_map() { - let input = [2, 4, 6, 8, 10]; - let ans = vec_map(&input); - assert_eq!(ans, [4, 8, 12, 16, 20]); - } } diff --git a/exercises/07_structs/structs3.rs b/exercises/07_structs/structs3.rs index 69e5ced7..d42729e1 100644 --- a/exercises/07_structs/structs3.rs +++ b/exercises/07_structs/structs3.rs @@ -1,38 +1,28 @@ // Structs contain data, but can also have logic. In this exercise, we have -// defined the `Package` struct, and we want to test some logic attached to it. +// defined the `Fireworks` struct and a couple of functions that work with it. +// Turn these free-standing functions into methods and associated functions +// to express that relationship more clearly in the code. + +#![deny(clippy::use_self)] // practice using the `Self` type #[derive(Debug)] -struct Package { - sender_country: String, - recipient_country: String, - weight_in_grams: u32, +struct Fireworks { + rockets: usize, } -impl Package { - fn new(sender_country: String, recipient_country: String, weight_in_grams: u32) -> Self { - if weight_in_grams < 10 { - // This isn't how you should handle errors in Rust, but we will - // learn about error handling later. - panic!("Can't ship a package with weight below 10 grams"); - } +// TODO: Turn this function into an associated function on `Fireworks`. +fn new_fireworks() -> Fireworks { + Fireworks { rockets: 0 } +} - Self { - sender_country, - recipient_country, - weight_in_grams, - } - } +// TODO: Turn this function into a method on `Fireworks`. +fn add_rockets(fireworks: &mut Fireworks, rockets: usize) { + fireworks.rockets += rockets +} - // TODO: Add the correct return type to the function signature. - fn is_international(&self) { - // TODO: Read the tests that use this method to find out when a package - // is considered international. - } - - // TODO: Add the correct return type to the function signature. - fn get_fees(&self, cents_per_gram: u32) { - // TODO: Calculate the package's fees. - } +// TODO: Turn this function into a method on `Fireworks`. +fn start(fireworks: Fireworks) -> String { + "πŸš€".repeat(fireworks.rockets) } fn main() { @@ -44,44 +34,18 @@ mod tests { use super::*; #[test] - #[should_panic] - fn fail_creating_weightless_package() { - let sender_country = String::from("Spain"); - let recipient_country = String::from("Austria"); + fn start_some_fireworks() { + let f = Fireworks::new(); + assert_eq!(f.start(), ""); - Package::new(sender_country, recipient_country, 5); - } + let mut f = Fireworks::new(); + f.add_rockets(3); + assert_eq!(f.start(), "πŸš€πŸš€πŸš€"); - #[test] - fn create_international_package() { - let sender_country = String::from("Spain"); - let recipient_country = String::from("Russia"); - - let package = Package::new(sender_country, recipient_country, 1200); - - assert!(package.is_international()); - } - - #[test] - fn create_local_package() { - let sender_country = String::from("Canada"); - let recipient_country = sender_country.clone(); - - let package = Package::new(sender_country, recipient_country, 1200); - - assert!(!package.is_international()); - } - - #[test] - fn calculate_transport_fees() { - let sender_country = String::from("Spain"); - let recipient_country = String::from("Spain"); - - let cents_per_gram = 3; - - let package = Package::new(sender_country, recipient_country, 1500); - - assert_eq!(package.get_fees(cents_per_gram), 4500); - assert_eq!(package.get_fees(cents_per_gram * 2), 9000); + let mut f = Fireworks::new(); + f.add_rockets(7); + // We don't use method syntax in the last test to ensure the `start` + // function takes ownership of the fireworks. + assert_eq!(Fireworks::start(f), "πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€"); } } diff --git a/exercises/09_strings/README.md b/exercises/09_strings/README.md index fa2104cc..df957d58 100644 --- a/exercises/09_strings/README.md +++ b/exercises/09_strings/README.md @@ -1,9 +1,11 @@ # Strings -Rust has two string types, a string slice (`&str`) and an owned string (`String`). +Rust has two string types: a string slice (`&str`) and an owned string (`String`). We're not going to dictate when you should use which one, but we'll show you how to identify and create them, as well as use them. ## Further information -- [Strings](https://doc.rust-lang.org/book/ch08-02-strings.html) +- [Strings (Rust Book)](https://doc.rust-lang.org/book/ch08-02-strings.html) +- [`str` methods](https://doc.rust-lang.org/std/primitive.str.html) +- [`String` methods](https://doc.rust-lang.org/std/string/struct.String.html) diff --git a/exercises/18_iterators/iterators1.rs b/exercises/18_iterators/iterators1.rs index ca937ed0..8014f0f1 100644 --- a/exercises/18_iterators/iterators1.rs +++ b/exercises/18_iterators/iterators1.rs @@ -10,9 +10,9 @@ fn main() { mod tests { #[test] fn iterators() { - let my_fav_fruits = ["banana", "custard apple", "avocado", "peach", "raspberry"]; + let my_fav_fruits = &["banana", "custard apple", "avocado", "peach", "raspberry"]; - // TODO: Create an iterator over the array. + // TODO: Create an iterator over the slice. let mut fav_fruits_iterator = todo!(); assert_eq!(fav_fruits_iterator.next(), Some(&"banana")); diff --git a/exercises/22_clippy/clippy3.rs b/exercises/22_clippy/clippy3.rs index 7a3cb390..3f23aaee 100644 --- a/exercises/22_clippy/clippy3.rs +++ b/exercises/22_clippy/clippy3.rs @@ -1,7 +1,6 @@ -// Here are some more easy Clippy fixes so you can see its utility πŸ“Ž +// Here are some more easy Clippy fixes so you can see its utility. // TODO: Fix all the Clippy lints. -#[rustfmt::skip] #[allow(unused_variables, unused_assignments)] fn main() { let my_option: Option<&str> = None; @@ -11,14 +10,16 @@ fn main() { println!("{}", my_option.unwrap()); } + #[rustfmt::skip] let my_arr = &[ -1, -2, -3 -4, -5, -6 ]; println!("My array! Here it is: {my_arr:?}"); - let my_empty_vec = vec![1, 2, 3, 4, 5].resize(0, 5); - println!("This Vec is empty, see? {my_empty_vec:?}"); + let mut my_vec = vec![1, 2, 3, 4, 5]; + my_vec.resize(0, 5); + println!("This Vec is empty, see? {my_vec:?}"); let mut value_a = 45; let mut value_b = 66; diff --git a/exercises/README.md b/exercises/README.md index 1df5cc37..24ebd069 100644 --- a/exercises/README.md +++ b/exercises/README.md @@ -9,7 +9,7 @@ | vecs | Β§8.1 | | move_semantics | Β§4.1-2 | | structs | Β§5.1, Β§5.3 | -| enums | Β§6, Β§18.3 | +| enums | Β§6, Β§19.3 | | strings | Β§8.2 | | modules | Β§7 | | hashmaps | Β§8.3 | diff --git a/release-hook.sh b/release-hook.sh index 49349337..0e955d1f 100755 --- a/release-hook.sh +++ b/release-hook.sh @@ -4,7 +4,6 @@ set -e typos -cargo upgrades # Similar to CI cargo clippy -- --deny warnings diff --git a/rustlings-macros/Cargo.toml b/rustlings-macros/Cargo.toml index 5df648b2..5d123b51 100644 --- a/rustlings-macros/Cargo.toml +++ b/rustlings-macros/Cargo.toml @@ -16,7 +16,7 @@ include = [ proc-macro = true [dependencies] -quote = "1.0" +quote = "1" serde.workspace = true toml.workspace = true diff --git a/rustlings-macros/info.toml b/rustlings-macros/info.toml index 516fd321..d88b549e 100644 --- a/rustlings-macros/info.toml +++ b/rustlings-macros/info.toml @@ -21,7 +21,7 @@ get started, here are some notes about how Rustlings operates: final_message = """ We hope you enjoyed learning about the various aspects of Rust! -If you noticed any issues, don't hesitate to report them on Github. +If you noticed any issues, don't hesitate to report them on GitHub. You can also contribute your own exercises to help the greater community! Before reporting an issue or contributing, please read our guidelines: @@ -318,16 +318,7 @@ of the Rust book to learn more.""" name = "vecs2" dir = "05_vecs" hint = """ -In the first function, we create an empty vector and want to push new elements -to it. - -In the second function, we map the values of the input and collect them into -a vector. - -After you've completed both functions, decide for yourself which approach you -like better. - -What do you think is the more commonly used pattern under Rust developers?""" +Use the `.push()` method on the vector to push new elements to it.""" # MOVE SEMANTICS @@ -426,11 +417,10 @@ https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances- name = "structs3" dir = "07_structs" hint = """ -For `is_international`: What makes a package international? Seems related to -the places it goes through right? - -For `get_fees`: This method takes an additional argument, is there a field in -the `Package` struct that this relates to? +Methods and associated functions are both declared in an `impl MyType {}` +block. Methods have a `self`, `&self` or `&mut self` parameter, where `self` +implicitly has the type of the impl block. Associated functions do not have +a `self` parameter. Have a look in The Book to find out more about method implementations: https://doc.rust-lang.org/book/ch05-03-method-syntax.html""" @@ -449,7 +439,7 @@ dir = "08_enums" test = false hint = """ You can create enumerations that have different variants with different types -such as anonymous structs, structs, a single string, tuples, no data, etc.""" +such as struct-like, tuple-like and unit-only variants.""" [[exercises]] name = "enums3" diff --git a/rustlings-macros/src/lib.rs b/rustlings-macros/src/lib.rs index b20c6f1d..40eebc16 100644 --- a/rustlings-macros/src/lib.rs +++ b/rustlings-macros/src/lib.rs @@ -3,20 +3,29 @@ use quote::quote; use serde::Deserialize; #[derive(Deserialize)] -struct ExerciseInfo { - name: String, - dir: String, +struct ExerciseInfo<'a> { + name: &'a str, + dir: &'a str, } #[derive(Deserialize)] -struct InfoFile { - exercises: Vec, +struct InfoFile<'a> { + #[serde(borrow)] + exercises: Vec>, } #[proc_macro] pub fn include_files(_: TokenStream) -> TokenStream { - let info_file = include_str!("../info.toml"); - let exercises = toml::de::from_str::(info_file) + // Remove `\r` on Windows + let info_file = String::from_utf8( + include_bytes!("../info.toml") + .iter() + .copied() + .filter(|c| *c != b'\r') + .collect(), + ) + .expect("Failed to parse `info.toml` as UTF8"); + let exercises = toml::de::from_str::(&info_file) .expect("Failed to parse `info.toml`") .exercises; @@ -37,7 +46,7 @@ pub fn include_files(_: TokenStream) -> TokenStream { continue; } - dirs.push(exercise.dir.as_str()); + dirs.push(exercise.dir); *dir_ind = dirs.len() - 1; } diff --git a/solutions/05_vecs/vecs2.rs b/solutions/05_vecs/vecs2.rs index 87f7625a..aae71038 100644 --- a/solutions/05_vecs/vecs2.rs +++ b/solutions/05_vecs/vecs2.rs @@ -8,22 +8,6 @@ fn vec_loop(input: &[i32]) -> Vec { output } -fn vec_map_example(input: &[i32]) -> Vec { - // 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 { - // We will dive deeper into iterators, but for now, this is all what you - // had to do! - // Advanced note: This method is more efficient because it automatically - // preallocates enough capacity. This can be done manually in `vec_loop` - // using `Vec::with_capacity(input.len())` instead of `Vec::new()`. - input.iter().map(|element| 2 * element).collect() -} - fn main() { // You can optionally experiment here. } @@ -38,18 +22,4 @@ mod tests { let ans = vec_loop(&input); assert_eq!(ans, [4, 8, 12, 16, 20]); } - - #[test] - fn test_vec_map_example() { - let input = [1, 2, 3]; - let ans = vec_map_example(&input); - assert_eq!(ans, [2, 3, 4]); - } - - #[test] - fn test_vec_map() { - let input = [2, 4, 6, 8, 10]; - let ans = vec_map(&input); - assert_eq!(ans, [4, 8, 12, 16, 20]); - } } diff --git a/solutions/07_structs/structs3.rs b/solutions/07_structs/structs3.rs index 3f878cc8..c672bb19 100644 --- a/solutions/07_structs/structs3.rs +++ b/solutions/07_structs/structs3.rs @@ -1,33 +1,21 @@ +#![deny(clippy::use_self)] // practice using the `Self` type + #[derive(Debug)] -struct Package { - sender_country: String, - recipient_country: String, - weight_in_grams: u32, +struct Fireworks { + rockets: usize, } -impl Package { - fn new(sender_country: String, recipient_country: String, weight_in_grams: u32) -> Self { - if weight_in_grams < 10 { - // This isn't how you should handle errors in Rust, but we will - // learn about error handling later. - panic!("Can't ship a package with weight below 10 grams"); - } - - Self { - sender_country, - recipient_country, - weight_in_grams, - } +impl Fireworks { + fn new() -> Self { + Self { rockets: 0 } } - fn is_international(&self) -> bool { - // ^^^^^^^ added - self.sender_country != self.recipient_country + fn add_rockets(&mut self, rockets: usize) { + self.rockets += rockets } - fn get_fees(&self, cents_per_gram: u32) -> u32 { - // ^^^^^^ added - self.weight_in_grams * cents_per_gram + fn start(self) -> String { + "πŸš€".repeat(self.rockets) } } @@ -40,44 +28,18 @@ mod tests { use super::*; #[test] - #[should_panic] - fn fail_creating_weightless_package() { - let sender_country = String::from("Spain"); - let recipient_country = String::from("Austria"); + fn start_some_fireworks() { + let f = Fireworks::new(); + assert_eq!(f.start(), ""); - Package::new(sender_country, recipient_country, 5); - } + let mut f = Fireworks::new(); + f.add_rockets(3); + assert_eq!(f.start(), "πŸš€πŸš€πŸš€"); - #[test] - fn create_international_package() { - let sender_country = String::from("Spain"); - let recipient_country = String::from("Russia"); - - let package = Package::new(sender_country, recipient_country, 1200); - - assert!(package.is_international()); - } - - #[test] - fn create_local_package() { - let sender_country = String::from("Canada"); - let recipient_country = sender_country.clone(); - - let package = Package::new(sender_country, recipient_country, 1200); - - assert!(!package.is_international()); - } - - #[test] - fn calculate_transport_fees() { - let sender_country = String::from("Spain"); - let recipient_country = String::from("Spain"); - - let cents_per_gram = 3; - - let package = Package::new(sender_country, recipient_country, 1500); - - assert_eq!(package.get_fees(cents_per_gram), 4500); - assert_eq!(package.get_fees(cents_per_gram * 2), 9000); + let mut f = Fireworks::new(); + f.add_rockets(7); + // We don't use method syntax in the last test to ensure the `start` + // function takes ownership of the fireworks. + assert_eq!(Fireworks::start(f), "πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€"); } } diff --git a/solutions/09_strings/strings4.rs b/solutions/09_strings/strings4.rs index 3c69b976..087b0386 100644 --- a/solutions/09_strings/strings4.rs +++ b/solutions/09_strings/strings4.rs @@ -19,7 +19,7 @@ fn main() { // `.into()` converts a type into an expected type. // If it is called where `String` is expected, it will convert `&str` to `String`. string("nice weather".into()); - // But if it is called where `&str` is expected, then `&str` is kept `&str` since no conversion is needed. + // But if it is called where `&str` is expected, then `&str` is kept as `&str` since no conversion is needed. // If you remove the `#[allow(…)]` line, then Clippy will tell you to remove `.into()` below since it is a useless conversion. #[allow(clippy::useless_conversion)] string_slice("nice weather".into()); diff --git a/solutions/11_hashmaps/hashmaps1.rs b/solutions/11_hashmaps/hashmaps1.rs index 3a787c43..0a654b88 100644 --- a/solutions/11_hashmaps/hashmaps1.rs +++ b/solutions/11_hashmaps/hashmaps1.rs @@ -1,7 +1,7 @@ // A basket of fruits in the form of a hash map needs to be defined. The key // represents the name of the fruit and the value represents how many of that // particular fruit is in the basket. You have to put at least 3 different -// types of fruits (e.g apple, banana, mango) in the basket and the total count +// types of fruits (e.g. apple, banana, mango) in the basket and the total count // of all the fruits should be at least 5. use std::collections::HashMap; diff --git a/solutions/12_options/options1.rs b/solutions/12_options/options1.rs index 4d615dd6..a8e6457d 100644 --- a/solutions/12_options/options1.rs +++ b/solutions/12_options/options1.rs @@ -1,8 +1,8 @@ -// This function returns how much icecream there is left in the fridge. +// This function returns how much ice cream there is left in the fridge. // If it's before 22:00 (24-hour system), then 5 scoops are left. At 22:00, -// someone eats it all, so no icecream is left (value 0). Return `None` if +// someone eats it all, so no ice cream is left (value 0). Return `None` if // `hour_of_day` is higher than 23. -fn maybe_icecream(hour_of_day: u16) -> Option { +fn maybe_ice_cream(hour_of_day: u16) -> Option { match hour_of_day { 0..=21 => Some(5), 22..=23 => Some(0), @@ -21,19 +21,19 @@ mod tests { #[test] fn raw_value() { // Using `unwrap` is fine in a test. - let icecreams = maybe_icecream(12).unwrap(); + let ice_creams = maybe_ice_cream(12).unwrap(); - assert_eq!(icecreams, 5); + assert_eq!(ice_creams, 5); } #[test] - fn check_icecream() { - assert_eq!(maybe_icecream(0), Some(5)); - assert_eq!(maybe_icecream(9), Some(5)); - assert_eq!(maybe_icecream(18), Some(5)); - assert_eq!(maybe_icecream(22), Some(0)); - assert_eq!(maybe_icecream(23), Some(0)); - assert_eq!(maybe_icecream(24), None); - assert_eq!(maybe_icecream(25), None); + fn check_ice_cream() { + assert_eq!(maybe_ice_cream(0), Some(5)); + assert_eq!(maybe_ice_cream(9), Some(5)); + assert_eq!(maybe_ice_cream(18), Some(5)); + assert_eq!(maybe_ice_cream(22), Some(0)); + assert_eq!(maybe_ice_cream(23), Some(0)); + assert_eq!(maybe_ice_cream(24), None); + assert_eq!(maybe_ice_cream(25), None); } } diff --git a/solutions/13_error_handling/errors5.rs b/solutions/13_error_handling/errors5.rs index c1424eee..93ab3b9d 100644 --- a/solutions/13_error_handling/errors5.rs +++ b/solutions/13_error_handling/errors5.rs @@ -6,7 +6,7 @@ // // In short, this particular use case for boxes is for when you want to own a // value and you care only that it is a type which implements a particular -// trait. To do so, The `Box` is declared as of type `Box` where +// trait. To do so, the `Box` is declared as of type `Box` where // `Trait` is the trait the compiler looks for on any value used in that // context. For this exercise, that context is the potential errors which // can be returned in a `Result`. diff --git a/solutions/16_lifetimes/lifetimes2.rs b/solutions/16_lifetimes/lifetimes2.rs index 3ca49093..412c6021 100644 --- a/solutions/16_lifetimes/lifetimes2.rs +++ b/solutions/16_lifetimes/lifetimes2.rs @@ -4,7 +4,7 @@ fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { fn main() { let string1 = String::from("long string is long"); - // Solution1: You can move `strings2` out of the inner block so that it is + // Solution 1: You can move `strings2` out of the inner block so that it is // not dropped before the print statement. let string2 = String::from("xyz"); let result; @@ -21,7 +21,7 @@ fn main() { { let string2 = String::from("xyz"); result = longest(&string1, &string2); - // Solution2: You can move the print statement into the inner block so + // Solution 2: You can move the print statement into the inner block so // that it is executed before `string2` is dropped. println!("The longest string is '{result}'"); // `string2` dropped here (end of the inner scope). diff --git a/solutions/18_iterators/iterators1.rs b/solutions/18_iterators/iterators1.rs index 93a6008a..79977fa4 100644 --- a/solutions/18_iterators/iterators1.rs +++ b/solutions/18_iterators/iterators1.rs @@ -10,9 +10,9 @@ fn main() { mod tests { #[test] fn iterators() { - let my_fav_fruits = ["banana", "custard apple", "avocado", "peach", "raspberry"]; + let my_fav_fruits = &["banana", "custard apple", "avocado", "peach", "raspberry"]; - // Create an iterator over the array. + // Create an iterator over the slice. let mut fav_fruits_iterator = my_fav_fruits.iter(); assert_eq!(fav_fruits_iterator.next(), Some(&"banana")); diff --git a/solutions/21_macros/macros3.rs b/solutions/21_macros/macros3.rs index df35be4d..9d574f89 100644 --- a/solutions/21_macros/macros3.rs +++ b/solutions/21_macros/macros3.rs @@ -1,4 +1,4 @@ -// Added the attribute `macro_use` attribute. +// Added the `macro_use` attribute. #[macro_use] mod macros { macro_rules! my_macro { diff --git a/solutions/22_clippy/clippy3.rs b/solutions/22_clippy/clippy3.rs index 81f381e0..8fc26704 100644 --- a/solutions/22_clippy/clippy3.rs +++ b/solutions/22_clippy/clippy3.rs @@ -1,6 +1,5 @@ use std::mem; -#[rustfmt::skip] #[allow(unused_variables, unused_assignments)] fn main() { let my_option: Option<&str> = None; @@ -11,17 +10,18 @@ fn main() { } // A comma was missing. + #[rustfmt::skip] let my_arr = &[ -1, -2, -3, -4, -5, -6, ]; println!("My array! Here it is: {my_arr:?}"); - let mut my_empty_vec = vec![1, 2, 3, 4, 5]; + let mut my_vec = vec![1, 2, 3, 4, 5]; // `resize` mutates a vector instead of returning a new one. // `resize(0, …)` clears a vector, so it is better to use `clear`. - my_empty_vec.clear(); - println!("This Vec is empty, see? {my_empty_vec:?}"); + my_vec.clear(); + println!("This Vec is empty, see? {my_vec:?}"); let mut value_a = 45; let mut value_b = 66; diff --git a/src/app_state.rs b/src/app_state.rs index d654d042..78b9c205 100644 --- a/src/app_state.rs +++ b/src/app_state.rs @@ -2,7 +2,6 @@ use anyhow::{Context, Error, Result, bail}; use crossterm::{QueueableCommand, cursor, terminal}; use std::{ collections::HashSet, - env, fs::{File, OpenOptions}, io::{Read, Seek, StdoutLock, Write}, path::{MAIN_SEPARATOR_STR, Path}, @@ -17,6 +16,7 @@ use std::{ use crate::{ clear_terminal, cmd::CmdRunner, + editor::{Editor, EditorJoinHandle}, embedded::EMBEDDED_FILES, exercise::{Exercise, RunnableExercise}, info_file::ExerciseInfo, @@ -52,21 +52,24 @@ pub enum CheckProgress { pub struct AppState { current_exercise_ind: usize, exercises: Vec, - // Caches the number of done exercises to avoid iterating over all exercises every time. - n_done: u16, - final_message: String, + // Cache the number of done exercises to avoid iterating over all exercises every time. + n_done: u32, + final_message: &'static str, state_file: File, // Preallocated buffer for reading and writing the state file. file_buf: Vec, official_exercises: bool, cmd_runner: CmdRunner, emit_file_links: bool, + editor: Option, } impl AppState { pub fn new( exercise_infos: Vec, - final_message: String, + final_message: &'static str, + editor: Option, + vs_code_term: bool, ) -> Result<(Self, StateFileStatus)> { let cmd_runner = CmdRunner::build()?; let mut state_file = OpenOptions::new() @@ -83,43 +86,38 @@ impl AppState { let mut exercises = exercise_infos .into_iter() .map(|exercise_info| { - // Leaking to be able to borrow in the watch mode `Table`. - // Leaking is not a problem because the `AppState` instance lives until - // the end of the program. - let path = exercise_info.path().leak(); - let name = exercise_info.name.leak(); - let dir = exercise_info.dir.map(|dir| &*dir.leak()); - let hint = exercise_info.hint.leak().trim_ascii(); - let canonical_path = dir_canonical_path.as_deref().map(|dir_canonical_path| { let mut canonical_path; - if let Some(dir) = dir { + if let Some(dir) = exercise_info.dir { canonical_path = String::with_capacity( - 2 + dir_canonical_path.len() + dir.len() + name.len(), + 2 + dir_canonical_path.len() + dir.len() + exercise_info.name.len(), ); canonical_path.push_str(dir_canonical_path); canonical_path.push_str(MAIN_SEPARATOR_STR); canonical_path.push_str(dir); } else { - canonical_path = - String::with_capacity(1 + dir_canonical_path.len() + name.len()); + canonical_path = String::with_capacity( + 1 + dir_canonical_path.len() + exercise_info.name.len(), + ); canonical_path.push_str(dir_canonical_path); } canonical_path.push_str(MAIN_SEPARATOR_STR); - canonical_path.push_str(name); + canonical_path.push_str(exercise_info.name); canonical_path.push_str(".rs"); canonical_path }); Exercise { - dir, - name, - path, + name: exercise_info.name, + dir: exercise_info.dir, + // Leaking for `Editor::open`. + // Leaking is fine since the app state exists until the end of the program. + path: exercise_info.path().leak(), canonical_path, test: exercise_info.test, strict_clippy: exercise_info.strict_clippy, - hint, + hint: exercise_info.hint.trim_ascii(), // Updated below. done: false, } @@ -181,43 +179,37 @@ impl AppState { official_exercises: !Path::new("info.toml").exists(), cmd_runner, // VS Code has its own file link handling - emit_file_links: env::var_os("TERM_PROGRAM").is_none_or(|v| v != "vscode"), + emit_file_links: !vs_code_term, + editor, }; Ok((slf, state_file_status)) } - #[inline] pub fn current_exercise_ind(&self) -> usize { self.current_exercise_ind } - #[inline] pub fn exercises(&self) -> &[Exercise] { &self.exercises } - #[inline] - pub fn n_done(&self) -> u16 { + pub fn n_done(&self) -> u32 { self.n_done } - #[inline] - pub fn n_pending(&self) -> u16 { - self.exercises.len() as u16 - self.n_done + pub fn n_pending(&self) -> u32 { + self.exercises.len() as u32 - self.n_done } - #[inline] pub fn current_exercise(&self) -> &Exercise { &self.exercises[self.current_exercise_ind] } - #[inline] pub fn cmd_runner(&self) -> &CmdRunner { &self.cmd_runner } - #[inline] pub fn emit_file_links(&self) -> bool { self.emit_file_links } @@ -343,12 +335,10 @@ impl AppState { Ok(()) } - pub fn reset_current_exercise(&mut self) -> Result<&'static str> { + pub fn reset_current_exercise(&mut self) -> Result<()> { self.set_pending(self.current_exercise_ind)?; let exercise = self.current_exercise(); - self.reset(self.current_exercise_ind, exercise.path)?; - - Ok(exercise.path) + self.reset(self.current_exercise_ind, exercise.path) } // Reset the exercise by index and return its name. @@ -440,7 +430,7 @@ impl AppState { .is_err() { break; - }; + } let success = exercise.run_exercise(None, &slf.cmd_runner); let progress = match success { @@ -567,6 +557,28 @@ impl AppState { Ok(()) } + + pub fn open_editor(&mut self) -> Result { + if let Some(editor) = self.editor.take() { + return editor.open(self.current_exercise_ind, self.current_exercise().path); + } + + Ok(EditorJoinHandle::default()) + } + + pub fn join_editor_handle(&mut self, handle: EditorJoinHandle) -> Result<()> { + self.editor = handle.join()?; + + Ok(()) + } + + pub fn close_editor(&mut self) -> Result<()> { + if let Some(editor) = &mut self.editor { + editor.close()?; + } + + Ok(()) + } } const BAD_INDEX_ERR: &str = "The current exercise index is higher than the number of exercises"; @@ -599,8 +611,8 @@ mod tests { fn dummy_exercise() -> Exercise { Exercise { - dir: None, name: "0", + dir: None, path: "exercises/0.rs", canonical_path: None, test: false, @@ -616,12 +628,13 @@ mod tests { current_exercise_ind: 0, exercises: vec![dummy_exercise(), dummy_exercise(), dummy_exercise()], n_done: 0, - final_message: String::new(), + final_message: "", state_file: tempfile::tempfile().unwrap(), file_buf: Vec::new(), official_exercises: true, cmd_runner: CmdRunner::build().unwrap(), emit_file_links: true, + editor: None, }; let mut assert = |done: [bool; 3], expected: [Option; 3]| { diff --git a/src/cargo_toml.rs b/src/cargo_toml.rs index ce0dfd0c..9297da82 100644 --- a/src/cargo_toml.rs +++ b/src/cargo_toml.rs @@ -38,7 +38,7 @@ pub fn append_bins( buf.extend_from_slice(b"\", path = \""); buf.extend_from_slice(exercise_path_prefix); buf.extend_from_slice(b"exercises/"); - if let Some(dir) = &exercise_info.dir { + if let Some(dir) = exercise_info.dir { buf.extend_from_slice(dir.as_bytes()); buf.push(b'/'); } @@ -56,7 +56,7 @@ pub fn append_bins( buf.extend_from_slice(b"\", path = \""); buf.extend_from_slice(exercise_path_prefix); buf.extend_from_slice(b"solutions/"); - if let Some(dir) = &exercise_info.dir { + if let Some(dir) = exercise_info.dir { buf.extend_from_slice(dir.as_bytes()); buf.push(b'/'); } @@ -106,19 +106,19 @@ mod tests { fn test_bins() { let exercise_infos = [ ExerciseInfo { - name: String::from("1"), + name: "1", dir: None, test: true, strict_clippy: true, - hint: String::new(), + hint: "", skip_check_unsolved: false, }, ExerciseInfo { - name: String::from("2"), - dir: Some(String::from("d")), + name: "2", + dir: Some("d"), test: false, strict_clippy: false, - hint: String::new(), + hint: "", skip_check_unsolved: false, }, ]; diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 00000000..153994be --- /dev/null +++ b/src/cli.rs @@ -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, + /// 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, + /// 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, + }, + /// 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, + }, + /// Commands for developing (community) Rustlings exercises + #[command(subcommand)] + Dev(DevCommand), +} diff --git a/src/cmd.rs b/src/cmd.rs index b2c58f6a..6442e449 100644 --- a/src/cmd.rs +++ b/src/cmd.rs @@ -126,7 +126,6 @@ pub struct CargoSubcommand<'out> { } impl CargoSubcommand<'_> { - #[inline] pub fn args<'arg, I>(&mut self, args: I) -> &mut Self where I: IntoIterator, @@ -136,7 +135,6 @@ impl CargoSubcommand<'_> { } /// The boolean in the returned `Result` is true if the command's exit status is success. - #[inline] pub fn run(self, description: &str) -> Result { run_cmd(self.cmd, description, self.output) } diff --git a/src/dev.rs b/src/dev.rs index 41fddbeb..f2be6066 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -7,7 +7,7 @@ mod new; mod update; #[derive(Subcommand)] -pub enum DevCommands { +pub enum DevCommand { /// Create a new project for community exercises New { /// The path to create the project in @@ -26,7 +26,7 @@ pub enum DevCommands { Update, } -impl DevCommands { +impl DevCommand { pub fn run(self) -> Result<()> { match self { Self::New { path, no_git } => { diff --git a/src/dev/check.rs b/src/dev/check.rs index f7111063..16d04563 100644 --- a/src/dev/check.rs +++ b/src/dev/check.rs @@ -63,7 +63,7 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result> { let mut file_buf = String::with_capacity(1 << 14); for exercise_info in &info_file.exercises { - let name = exercise_info.name.as_str(); + let name = exercise_info.name; if name.is_empty() { bail!("Found an empty exercise name in `info.toml`"); } @@ -76,7 +76,7 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result> { bail!("Char `{c}` in the exercise name `{name}` is not allowed"); } - if let Some(dir) = &exercise_info.dir { + if let Some(dir) = exercise_info.dir { if dir.is_empty() { bail!("The exercise `{name}` has an empty dir name in `info.toml`"); } @@ -214,7 +214,7 @@ fn check_exercises_unsolved( Some( thread::Builder::new() .spawn(|| exercise_info.run_exercise(None, cmd_runner)) - .map(|handle| (exercise_info.name.as_str(), handle)), + .map(|handle| (exercise_info.name, handle)), ) }) .collect::, _>>() diff --git a/src/editor.rs b/src/editor.rs new file mode 100644 index 00000000..3c189c78 --- /dev/null +++ b/src/editor.rs @@ -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> { + 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), + Zellij(Option<(String, u32, usize)>), +} + +impl Editor { + pub fn new(cmd: Option, vs_code_term: bool) -> Result> { + 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 { + 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>>); + +impl EditorJoinHandle { + pub fn join(self) -> Result> { + if let Some(handle) = self.0 { + let editor = handle.join().unwrap()?; + return Ok(Some(editor)); + } + + Ok(None) + } +} diff --git a/src/editor/zellij.rs b/src/editor/zellij.rs new file mode 100644 index 00000000..b628a682 --- /dev/null +++ b/src/editor/zellij.rs @@ -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 { + 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::>(&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(()) +} diff --git a/src/embedded.rs b/src/embedded.rs index 61a5f581..bee4119c 100644 --- a/src/embedded.rs +++ b/src/embedded.rs @@ -85,7 +85,7 @@ impl EmbeddedFiles { exercise_path.truncate(prefix.len()); exercise_path.push_str(dir.name); exercise_path.push('/'); - exercise_path.push_str(&exercise_info.name); + exercise_path.push_str(exercise_info.name); exercise_path.push_str(".rs"); fs::write(&exercise_path, exercise_files.exercise) @@ -141,13 +141,14 @@ mod tests { use super::*; #[derive(Deserialize)] - struct ExerciseInfo { - dir: String, + struct ExerciseInfo<'a> { + dir: &'a str, } #[derive(Deserialize)] - struct InfoFile { - exercises: Vec, + struct InfoFile<'a> { + #[serde(borrow)] + exercises: Vec>, } #[test] diff --git a/src/exercise.rs b/src/exercise.rs index a0596b5b..b969c69a 100644 --- a/src/exercise.rs +++ b/src/exercise.rs @@ -66,8 +66,8 @@ fn run_bin( /// See `info_file::ExerciseInfo` pub struct Exercise { - pub dir: Option<&'static str>, pub name: &'static str, + pub dir: Option<&'static str>, /// Path of the exercise file starting with the `exercises/` directory. pub path: &'static str, pub canonical_path: Option, @@ -158,7 +158,6 @@ pub trait RunnableExercise { /// Compile, check and run the exercise. /// The output is written to the `output` buffer after clearing it. - #[inline] fn run_exercise(&self, output: Option<&mut Vec>, cmd_runner: &CmdRunner) -> Result { self.run::(self.name(), output, cmd_runner) } @@ -201,22 +200,18 @@ pub trait RunnableExercise { } impl RunnableExercise for Exercise { - #[inline] fn name(&self) -> &str { self.name } - #[inline] fn dir(&self) -> Option<&str> { self.dir } - #[inline] fn strict_clippy(&self) -> bool { self.strict_clippy } - #[inline] fn test(&self) -> bool { self.test } diff --git a/src/info_file.rs b/src/info_file.rs index 04e5d644..26bb1a2c 100644 --- a/src/info_file.rs +++ b/src/info_file.rs @@ -8,9 +8,9 @@ use crate::{embedded::EMBEDDED_FILES, exercise::RunnableExercise}; #[derive(Deserialize)] pub struct ExerciseInfo { /// Exercise's unique name. - pub name: String, + pub name: &'static str, /// Exercise's directory name inside the `exercises/` directory. - pub dir: Option, + pub dir: Option<&'static str>, /// Run `cargo test` on the exercise. #[serde(default = "default_true")] pub test: bool, @@ -18,12 +18,11 @@ pub struct ExerciseInfo { #[serde(default)] pub strict_clippy: bool, /// The exercise's hint to be shown to the user on request. - pub hint: String, + pub hint: &'static str, /// The exercise is already solved. Ignore it when checking that all exercises are unsolved. #[serde(default)] pub skip_check_unsolved: bool, } -#[inline(always)] const fn default_true() -> bool { true } @@ -31,7 +30,7 @@ const fn default_true() -> bool { impl ExerciseInfo { /// Path to the exercise file starting with the `exercises/` directory. pub fn path(&self) -> String { - let mut path = if let Some(dir) = &self.dir { + let mut path = if let Some(dir) = self.dir { // 14 = 10 + 1 + 3 // exercises/ + / + .rs let mut path = String::with_capacity(14 + dir.len() + self.name.len()); @@ -47,7 +46,7 @@ impl ExerciseInfo { path }; - path.push_str(&self.name); + path.push_str(self.name); path.push_str(".rs"); path @@ -55,22 +54,18 @@ impl ExerciseInfo { } impl RunnableExercise for ExerciseInfo { - #[inline] fn name(&self) -> &str { - &self.name + self.name } - #[inline] fn dir(&self) -> Option<&str> { - self.dir.as_deref() + self.dir } - #[inline] fn strict_clippy(&self) -> bool { self.strict_clippy } - #[inline] fn test(&self) -> bool { self.test } @@ -82,9 +77,9 @@ pub struct InfoFile { /// For possible breaking changes in the future for community exercises. pub format_version: u8, /// Shown to users when starting with the exercises. - pub welcome_message: Option, + pub welcome_message: Option<&'static str>, /// Shown to users after finishing all exercises. - pub final_message: Option, + pub final_message: Option<&'static str>, /// List of all exercises. pub exercises: Vec, } @@ -94,9 +89,17 @@ impl InfoFile { /// Community exercises: Parse the `info.toml` file in the current directory. pub fn parse() -> Result { // Read a local `info.toml` if it exists. - let slf = match fs::read_to_string("info.toml") { - Ok(file_content) => toml::de::from_str::(&file_content) - .context("Failed to parse the `info.toml` file")?, + let slf = match fs::read("info.toml") { + Ok(file_content) => { + // Remove `\r` on Windows. + // Leaking is fine since the info file is used until the end of the program. + let file_content = + String::from_utf8(file_content.into_iter().filter(|c| *c != b'\r').collect()) + .context("Failed to parse `info.toml` as UTF8")? + .leak(); + toml::de::from_str::(file_content) + .context("Failed to parse the `info.toml` file")? + } Err(e) => { if e.kind() == ErrorKind::NotFound { return toml::de::from_str(EMBEDDED_FILES.info_file) diff --git a/src/init.rs b/src/init.rs index 68011ed4..f043bd48 100644 --- a/src/init.rs +++ b/src/init.rs @@ -5,10 +5,10 @@ use crossterm::{ }; use serde::Deserialize; use std::{ - env::set_current_dir, + env::{current_dir, set_current_dir}, fs::{self, create_dir}, io::{self, Write}, - path::{Path, PathBuf}, + path::Path, process::{Command, Stdio}, }; @@ -18,8 +18,9 @@ use crate::{ }; #[derive(Deserialize)] -struct CargoLocateProject { - root: PathBuf, +struct CargoLocateProject<'a> { + #[serde(borrow)] + root: &'a Path, } pub fn init() -> Result<()> { @@ -72,9 +73,9 @@ pub fn init() -> Result<()> { )? .root; - let workspace_manifest_content = fs::read_to_string(&workspace_manifest) + let workspace_manifest_content = fs::read_to_string(workspace_manifest) .with_context(|| format!("Failed to read the file {}", workspace_manifest.display()))?; - if !workspace_manifest_content.contains("[workspace]\n") + if !workspace_manifest_content.contains("[workspace]") && !workspace_manifest_content.contains("workspace.") { bail!( @@ -106,7 +107,7 @@ pub fn init() -> Result<()> { } stdout.write_all(b"The directory `rustlings` has been added to `workspace.members` in the `Cargo.toml` file of this Cargo workspace.\n")?; - fs::remove_dir_all("rustlings") + fs::remove_dir_all(rustlings_dir) .context("Failed to remove the temporary directory `rustlings/`")?; init_git = false; } else { @@ -168,14 +169,27 @@ pub fn init() -> Result<()> { fs::write(".vscode/extensions.json", VS_CODE_EXTENSIONS_JSON) .context("Failed to create the file `rustlings/.vscode/extensions.json`")?; - if init_git { - // 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(); + if init_git && let Ok(dir) = current_dir() { + let mut dir = dir.as_path(); + + loop { + if dir.join(".git").exists() || dir.join(".jj").exists() { + break; + } + + if let Some(parent) = dir.parent() { + dir = parent; + } else { + // Ignore any Git error because Git initialization is not required. + let _ = Command::new("git") + .arg("init") + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); + break; + } + } } stdout.queue(SetForegroundColor(Color::Green))?; diff --git a/src/list.rs b/src/list.rs index a2eee9e1..db466c2f 100644 --- a/src/list.rs +++ b/src/list.rs @@ -11,9 +11,10 @@ use crossterm::{ }; use std::io::{self, StdoutLock, Write}; -use crate::app_state::AppState; - -use self::state::{Filter, ListState}; +use crate::{ + app_state::AppState, + list::state::{Filter, ListState}, +}; mod scroll_state; mod state; @@ -82,7 +83,7 @@ fn handle_list(app_state: &mut AppState, stdout: &mut StdoutLock) -> Result<()> } } KeyCode::Char('r') => list_state.reset_selected()?, - KeyCode::Char('c') => { + KeyCode::Char('c') | KeyCode::Enter => { if list_state.selected_to_current_exercise()? { return Ok(()); } diff --git a/src/list/scroll_state.rs b/src/list/scroll_state.rs index 2c02ed4f..299db568 100644 --- a/src/list/scroll_state.rs +++ b/src/list/scroll_state.rs @@ -19,7 +19,6 @@ impl ScrollState { } } - #[inline] pub fn offset(&self) -> usize { self.offset } @@ -41,7 +40,6 @@ impl ScrollState { .min(global_max_offset); } - #[inline] pub fn selected(&self) -> Option { self.selected } @@ -86,12 +84,10 @@ impl ScrollState { self.set_selected(self.selected.map_or(0, |selected| selected.min(n_rows - 1))); } - #[inline] fn update_scroll_padding(&mut self) { self.scroll_padding = (self.max_n_rows_to_display / 4).min(self.max_scroll_padding); } - #[inline] pub fn max_n_rows_to_display(&self) -> usize { self.max_n_rows_to_display } diff --git a/src/list/state.rs b/src/list/state.rs index 4fd1301d..8b0c4df8 100644 --- a/src/list/state.rs +++ b/src/list/state.rs @@ -15,11 +15,10 @@ use std::{ use crate::{ app_state::AppState, exercise::Exercise, + list::scroll_state::ScrollState, term::{CountedWrite, MaxLenWriter, progress_bar}, }; -use super::scroll_state::ScrollState; - const COL_SPACING: usize = 2; const SELECTED_ROW_ATTRIBUTES: Attributes = Attributes::none() .with(Attribute::Reverse) @@ -229,7 +228,7 @@ impl<'a> ListState<'a> { progress_bar( &mut MaxLenWriter::new(stdout, self.term_width as usize), self.app_state.n_done(), - self.app_state.exercises().len() as u16, + self.app_state.exercises().len() as u32, self.term_width, )?; next_ln(stdout)?; @@ -238,11 +237,24 @@ impl<'a> ListState<'a> { if self.message.is_empty() { // Help footer message if self.scroll_state.selected().is_some() { - writer.write_str("↓/j ↑/k home/g end/G | ontinue at | eset exercise")?; + writer.write_str("↓/")?; + hotkey(&mut writer, b"j")?; + writer.write_str(" ↑/")?; + hotkey(&mut writer, b"k")?; + writer.write_ascii(b" home/")?; + hotkey(&mut writer, b"g")?; + writer.write_ascii(b" end/")?; + hotkey(&mut writer, b"G")?; + writer.write_str(" | ↩️/")?; + hotkey(&mut writer, b"c")?; + writer.write_ascii(b"ontinue at | ")?; + hotkey(&mut writer, b"r")?; + writer.write_ascii(b"eset exercise")?; next_ln(stdout)?; writer = MaxLenWriter::new(stdout, self.term_width as usize); - writer.write_ascii(b"earch | filter ")?; + hotkey(&mut writer, b"s")?; + writer.write_ascii(b"earch | filter ")?; } else { // Nothing selected (and nothing shown), so only display filter and quit. writer.write_ascii(b"filter ")?; @@ -250,27 +262,41 @@ impl<'a> ListState<'a> { match self.filter { Filter::Done => { + writer.stdout.queue(SetAttribute(Attribute::Underlined))?; + hotkey(&mut writer, b"d")?; writer .stdout .queue(SetForegroundColor(Color::Magenta))? .queue(SetAttribute(Attribute::Underlined))?; - writer.write_ascii(b"one")?; + writer.write_str("one")?; writer.stdout.queue(ResetColor)?; - writer.write_ascii(b"/

ending")?; + writer.write_ascii(b"/")?; + hotkey(&mut writer, b"p")?; + writer.write_ascii(b"ending")?; } Filter::Pending => { - writer.write_ascii(b"one/")?; + hotkey(&mut writer, b"d")?; + writer.write_ascii(b"one/")?; + writer.stdout.queue(SetAttribute(Attribute::Underlined))?; + hotkey(&mut writer, b"p")?; writer .stdout .queue(SetForegroundColor(Color::Magenta))? .queue(SetAttribute(Attribute::Underlined))?; - writer.write_ascii(b"

ending")?; + writer.write_ascii(b"ending")?; writer.stdout.queue(ResetColor)?; } - Filter::None => writer.write_ascii(b"one/

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" | uit list")?; + writer.write_ascii(b" | ")?; + hotkey(&mut writer, b"q")?; + writer.write_ascii(b"uit list")?; } else { writer.stdout.queue(SetForegroundColor(Color::Magenta))?; writer.write_str(&self.message)?; @@ -304,7 +330,6 @@ impl<'a> ListState<'a> { self.scroll_state.set_n_rows(n_rows); } - #[inline] pub fn filter(&self) -> Filter { self.filter } @@ -314,22 +339,18 @@ impl<'a> ListState<'a> { self.update_rows(); } - #[inline] pub fn select_next(&mut self) { self.scroll_state.select_next(); } - #[inline] pub fn select_previous(&mut self) { self.scroll_state.select_previous(); } - #[inline] pub fn select_first(&mut self) { self.scroll_state.select_first(); } - #[inline] pub fn select_last(&mut self) { self.scroll_state.select_last(); } @@ -366,11 +387,11 @@ impl<'a> ListState<'a> { let exercise_ind = self.selected_to_exercise_ind(selected)?; let exercise_name = self.app_state.reset_exercise_by_ind(exercise_ind)?; - self.update_rows(); write!( self.message, "The exercise `{exercise_name}` has been reset", )?; + self.update_rows(); Ok(()) } @@ -415,3 +436,14 @@ impl<'a> ListState<'a> { Ok(true) } } + +/// Draw an emphasized hotkey in the list footer. +fn hotkey(writer: &mut MaxLenWriter, hotkey: &[u8]) -> io::Result<()> { + writer + .stdout + .queue(SetForegroundColor(Color::Yellow))? + .queue(SetAttribute(Attribute::Bold))?; + writer.write_ascii(hotkey)?; + writer.stdout.queue(ResetColor)?; + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index ffd2dfa7..8da36f7f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,27 @@ use anyhow::{Context, Result, bail}; use app_state::StateFileStatus; -use clap::{Parser, Subcommand}; +use clap::Parser; use std::{ + env, io::{self, IsTerminal, Write}, path::Path, process::ExitCode, }; use term::{clear_terminal, press_enter_prompt}; -use self::{app_state::AppState, dev::DevCommands, info_file::InfoFile}; +use crate::{ + app_state::AppState, + cli::{Args, Command}, + editor::Editor, + info_file::InfoFile, +}; mod app_state; mod cargo_toml; +mod cli; mod cmd; mod dev; +mod editor; mod embedded; mod exercise; mod info_file; @@ -25,44 +33,6 @@ mod watch; const CURRENT_FORMAT_VERSION: u8 = 1; -/// Rustlings is a collection of small exercises to get you used to writing and reading Rust code -#[derive(Parser)] -#[command(version)] -struct Args { - #[command(subcommand)] - command: Option, - /// 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, - }, - /// 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, - }, - /// Commands for developing (community) Rustlings exercises - #[command(subcommand)] - Dev(DevCommands), -} - fn main() -> Result { let args = Args::parse(); @@ -72,8 +42,8 @@ fn main() -> Result { 'priority_cmd: { match args.command { - Some(Subcommands::Init) => init::init().context("Initialization failed")?, - Some(Subcommands::Dev(dev_command)) => dev_command.run()?, + Some(Command::Init) => init::init().context("Initialization failed")?, + Some(Command::Dev(dev_command)) => dev_command.run()?, _ => break 'priority_cmd, } @@ -91,9 +61,18 @@ fn main() -> Result { bail!(FORMAT_VERSION_HIGHER_ERR); } + let vs_code_term = env::var_os("TERM_PROGRAM").is_some_and(|v| v == "vscode"); + let editor = if args.no_editor { + None + } else { + Editor::new(args.edit_cmd, vs_code_term)? + }; + let (mut app_state, state_file_status) = AppState::new( info_file.exercises, info_file.final_message.unwrap_or_default(), + editor, + vs_code_term, )?; // Show the welcome message if the state file doesn't exist yet. @@ -128,7 +107,7 @@ fn main() -> Result { None } else { // For the notify event handler thread. - // Leaking is not a problem because the slice lives until the end of the program. + // Leaking is fine since the slice is used until the end of the program. Some( &*app_state .exercises() @@ -140,14 +119,15 @@ fn main() -> Result { }; watch::watch(&mut app_state, notify_exercise_names)?; + app_state.close_editor()?; } - Some(Subcommands::Run { name }) => { + Some(Command::Run { name }) => { if let Some(name) = name { app_state.set_current_exercise_by_name(&name)?; } return run::run(&mut app_state); } - Some(Subcommands::CheckAll) => { + Some(Command::CheckAll) => { let mut stdout = io::stdout().lock(); if let Some(first_pending_exercise_ind) = app_state.check_all_exercises(&mut stdout)? { if app_state.current_exercise().done { @@ -171,23 +151,36 @@ fn main() -> Result { stdout.write_all(b"\n")?; 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)?; - let exercise_path = app_state.reset_current_exercise()?; - println!("The exercise {exercise_path} has been reset"); + app_state.reset_current_exercise()?; + + let current_exercise = app_state.current_exercise(); + let mut stdout = io::stdout().lock(); + stdout.write_all(b"The exercise ")?; + current_exercise.terminal_file_link(&mut stdout, app_state.emit_file_links())?; + stdout.write_all(b" has been reset\n")?; } - Some(Subcommands::Hint { name }) => { + Some(Command::Hint { name }) => { if let Some(name) = name { app_state.set_current_exercise_by_name(&name)?; } - println!("{}", app_state.current_exercise().hint); + + let current_exercise = app_state.current_exercise(); + let mut stdout = io::stdout().lock(); + stdout.write_all(b"Current exercise: ")?; + current_exercise.terminal_file_link(&mut stdout, app_state.emit_file_links())?; + + stdout.write_all(b"\n\nHint:\n")?; + stdout.write_all(current_exercise.hint.as_bytes())?; + stdout.write_all(b"\n")?; } // Handled in an earlier match. - Some(Subcommands::Init | Subcommands::Dev(_)) => (), + Some(Command::Init | Command::Dev(_)) => (), } Ok(ExitCode::SUCCESS) diff --git a/src/term.rs b/src/term.rs index 3d149b33..2467b450 100644 --- a/src/term.rs +++ b/src/term.rs @@ -18,7 +18,6 @@ pub struct MaxLenWriter<'a, 'lock> { } impl<'a, 'lock> MaxLenWriter<'a, 'lock> { - #[inline] pub fn new(stdout: &'a mut StdoutLock<'lock>, max_len: usize) -> Self { Self { stdout, @@ -28,7 +27,6 @@ impl<'a, 'lock> MaxLenWriter<'a, 'lock> { } // Additional is for emojis that take more space. - #[inline] pub fn add_to_len(&mut self, additional: usize) { self.len += additional; } @@ -64,24 +62,20 @@ impl<'lock> CountedWrite<'lock> for MaxLenWriter<'_, 'lock> { Ok(()) } - #[inline] fn stdout(&mut self) -> &mut StdoutLock<'lock> { self.stdout } } impl<'a> CountedWrite<'a> for StdoutLock<'a> { - #[inline] fn write_ascii(&mut self, ascii: &[u8]) -> io::Result<()> { self.write_all(ascii) } - #[inline] fn write_str(&mut self, unicode: &str) -> io::Result<()> { self.write_all(unicode.as_bytes()) } - #[inline] fn stdout(&mut self) -> &mut StdoutLock<'a> { self } @@ -193,19 +187,19 @@ impl Drop for ProgressCounter<'_, '_> { pub fn progress_bar<'a>( writer: &mut impl CountedWrite<'a>, - progress: u16, - total: u16, + progress: u32, + total: u32, term_width: u16, ) -> io::Result<()> { - debug_assert!(total <= 999); - debug_assert!(progress <= total); - const PREFIX: &[u8] = b"Progress: ["; const PREFIX_WIDTH: u16 = PREFIX.len() as u16; const POSTFIX_WIDTH: u16 = "] xxx/xxx".len() as u16; const WRAPPER_WIDTH: u16 = PREFIX_WIDTH + POSTFIX_WIDTH; const MIN_LINE_WIDTH: u16 = WRAPPER_WIDTH + 4; + debug_assert!(total <= 999); + debug_assert!(progress <= total); + if term_width < MIN_LINE_WIDTH { writer.write_ascii(b"Progress: ")?; // Integers are in ASCII. @@ -215,7 +209,8 @@ pub fn progress_bar<'a>( let stdout = writer.stdout(); stdout.write_all(PREFIX)?; - let width = term_width - WRAPPER_WIDTH; + // Use u32 to prevent the intermediate multiplication from overflowing + let width = u32::from(term_width - WRAPPER_WIDTH); let filled = (width * progress) / total; stdout.queue(SetForegroundColor(Color::Green))?; @@ -225,14 +220,13 @@ pub fn progress_bar<'a>( if filled < width { stdout.write_all(b">")?; - } - let width_minus_filled = width - filled; - if width_minus_filled > 1 { - let red_part_width = width_minus_filled - 1; - stdout.queue(SetForegroundColor(Color::Red))?; - for _ in 0..red_part_width { - stdout.write_all(b"-")?; + let width_minus_filled = width - filled; + if width_minus_filled > 1 { + stdout.queue(SetForegroundColor(Color::Red))?; + for _ in 1..width_minus_filled { + stdout.write_all(b"-")?; + } } } diff --git a/src/watch.rs b/src/watch.rs index 3a56b4b6..857ccfde 100644 --- a/src/watch.rs +++ b/src/watch.rs @@ -13,10 +13,9 @@ use std::{ use crate::{ app_state::{AppState, ExercisesProgress}, list, + watch::{notify_event::NotifyEventHandler, state::WatchState, terminal_event::InputEvent}, }; -use self::{notify_event::NotifyEventHandler, state::WatchState, terminal_event::InputEvent}; - mod notify_event; mod state; mod terminal_event; @@ -28,7 +27,6 @@ static EXERCISE_RUNNING: AtomicBool = AtomicBool::new(false); pub struct InputPauseGuard(()); impl InputPauseGuard { - #[inline] pub fn scoped_pause() -> Self { EXERCISE_RUNNING.store(true, Relaxed); Self(()) @@ -36,7 +34,6 @@ impl InputPauseGuard { } impl Drop for InputPauseGuard { - #[inline] fn drop(&mut self) { EXERCISE_RUNNING.store(false, Relaxed); } @@ -175,8 +172,7 @@ pub fn watch( watch_list_loop(app_state, notify_exercise_names) } -const QUIT_MSG: &[u8] = b" - +const QUIT_MSG: &[u8] = b"q\n We hope you're enjoying learning Rust! If you want to continue working on the exercises at a later point, you can simply run `rustlings` again in this directory. "; diff --git a/src/watch/notify_event.rs b/src/watch/notify_event.rs index 9c05f10d..edd9c720 100644 --- a/src/watch/notify_event.rs +++ b/src/watch/notify_event.rs @@ -12,7 +12,7 @@ use std::{ time::Duration, }; -use super::{EXERCISE_RUNNING, WatchEvent}; +use crate::watch::{EXERCISE_RUNNING, WatchEvent}; const DEBOUNCE_DURATION: Duration = Duration::from_millis(200); diff --git a/src/watch/state.rs b/src/watch/state.rs index a92dd2d6..8bbdc585 100644 --- a/src/watch/state.rs +++ b/src/watch/state.rs @@ -17,10 +17,9 @@ use crate::{ clear_terminal, exercise::{OUTPUT_CAPACITY, RunnableExercise, solution_link_line}, term::progress_bar, + watch::{InputPauseGuard, WatchEvent, terminal_event::terminal_event_handler}, }; -use super::{InputPauseGuard, WatchEvent, terminal_event::terminal_event_handler}; - const HEADING_ATTRIBUTES: Attributes = Attributes::none() .with(Attribute::Bold) .with(Attribute::Underlined); @@ -60,7 +59,7 @@ impl<'a> WatchState<'a> { watch_event_sender, terminal_event_unpause_receiver, manual_run, - ) + ); }) .context("Failed to spawn a thread to handle terminal events")?; @@ -79,14 +78,16 @@ impl<'a> WatchState<'a> { // Ignore any input until running the exercise is done. let _input_pause_guard = InputPauseGuard::scoped_pause(); - self.show_hint = false; - writeln!( stdout, "\nChecking the exercise `{}`. Please wait…", self.app_state.current_exercise().name, )?; + let editor_handle = self.app_state.open_editor()?; + + self.show_hint = false; + let success = self .app_state .current_exercise() @@ -106,7 +107,9 @@ impl<'a> WatchState<'a> { self.done_status = DoneStatus::Pending; } + self.app_state.join_editor_handle(editor_handle)?; self.render(stdout)?; + Ok(()) } @@ -128,9 +131,10 @@ impl<'a> WatchState<'a> { match answer[0] { b'y' | b'Y' => { + self.app_state.close_editor()?; self.app_state.reset_current_exercise()?; - // The file watcher reruns the exercise otherwise. + // The file watcher reruns the exercise otherwise if self.manual_run { self.run_current_exercise(stdout)?; } @@ -245,7 +249,7 @@ impl<'a> WatchState<'a> { progress_bar( stdout, self.app_state.n_done(), - self.app_state.exercises().len() as u16, + self.app_state.exercises().len() as u32, self.term_width, )?; diff --git a/src/watch/terminal_event.rs b/src/watch/terminal_event.rs index 2400a3df..4f0685b6 100644 --- a/src/watch/terminal_event.rs +++ b/src/watch/terminal_event.rs @@ -4,7 +4,7 @@ use std::sync::{ mpsc::{Receiver, Sender}, }; -use super::{EXERCISE_RUNNING, WatchEvent}; +use crate::watch::{EXERCISE_RUNNING, WatchEvent}; pub enum InputEvent { Next, @@ -47,7 +47,7 @@ pub fn terminal_event_handler( // Pause input until quitting the confirmation prompt. if unpause_receiver.recv().is_err() { return; - }; + } continue; } @@ -64,7 +64,7 @@ pub fn terminal_event_handler( return; } } - Ok(Event::FocusGained | Event::FocusLost | Event::Mouse(_)) => continue, + Ok(Event::FocusGained | Event::FocusLost | Event::Mouse(_)) => (), Err(e) => break WatchEvent::TerminalEventErr(e), } }; diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index bb3a084b..d38d4e93 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,11 +1,9 @@ use std::{ - env::{self, consts::EXE_SUFFIX}, process::{Command, Stdio}, str::from_utf8, }; enum Output<'a> { - FullStdout(&'a str), PartialStdout(&'a str), PartialStderr(&'a str), } @@ -20,39 +18,24 @@ struct Cmd<'a> { } impl<'a> Cmd<'a> { - #[inline] fn current_dir(&mut self, current_dir: &'a str) -> &mut Self { self.current_dir = Some(current_dir); self } - #[inline] fn args(&mut self, args: &'a [&'a str]) -> &mut Self { self.args = args; self } - #[inline] fn output(&mut self, output: Output<'a>) -> &mut Self { self.output = Some(output); self } + #[track_caller] fn assert(&self, success: bool) { - let rustlings_bin = { - let mut path = env::current_exe().unwrap(); - // Pop test binary name - path.pop(); - // Pop `/deps` - path.pop(); - - path.push("rustlings"); - let mut path = path.into_os_string(); - path.push(EXE_SUFFIX); - path - }; - - let mut cmd = Command::new(rustlings_bin); + let mut cmd = Command::new(env!("CARGO_BIN_EXE_rustlings")); if let Some(current_dir) = self.current_dir { cmd.current_dir(current_dir); @@ -60,38 +43,32 @@ impl<'a> Cmd<'a> { cmd.args(self.args).stdin(Stdio::null()); - let status = match self.output { - None => cmd - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .unwrap(), - Some(FullStdout(stdout)) => { - let output = cmd.stderr(Stdio::null()).output().unwrap(); - assert_eq!(from_utf8(&output.stdout).unwrap(), stdout); - output.status - } + let output = cmd.output().unwrap(); + match self.output { + None => (), Some(PartialStdout(stdout)) => { - let output = cmd.stderr(Stdio::null()).output().unwrap(); assert!(from_utf8(&output.stdout).unwrap().contains(stdout)); - output.status } Some(PartialStderr(stderr)) => { - let output = cmd.stdout(Stdio::null()).output().unwrap(); assert!(from_utf8(&output.stderr).unwrap().contains(stderr)); - output.status } }; - assert_eq!(status.success(), success, "{cmd:?}"); + if output.status.success() != success { + panic!( + "{cmd:?}\n\nstdout:\n{}\n\nstderr:\n{}", + from_utf8(&output.stdout).unwrap(), + from_utf8(&output.stderr).unwrap(), + ); + } } - #[inline] + #[track_caller] fn success(&self) { self.assert(true); } - #[inline] + #[track_caller] fn fail(&self) { self.assert(false); } @@ -148,7 +125,7 @@ fn hint() { Cmd::default() .current_dir("tests/test_exercises") .args(&["hint", "test_failure"]) - .output(FullStdout("The answer to everything: 42\n")) + .output(PartialStdout("\n\nHint:\nThe answer to everything: 42\n")) .success(); } diff --git a/website/config.toml b/website/config.toml index 0c01dc7d..73ae7f2c 100644 --- a/website/config.toml +++ b/website/config.toml @@ -6,11 +6,11 @@ compile_sass = false build_search_index = false [markdown] -highlight_code = true -highlight_theme = "dracula" - insert_anchor_links = "heading" +[markdown.highlighting] +theme = "dracula" + [extra] logo_path = "images/happy_ferris.svg" diff --git a/website/content/community-exercises/index.md b/website/content/community-exercises/index.md index 8766783a..5d4d0d20 100644 --- a/website/content/community-exercises/index.md +++ b/website/content/community-exercises/index.md @@ -7,6 +7,7 @@ title = "Community Exercises" - πŸ‡―πŸ‡΅ [Japanese Rustlings](https://github.com/sotanengel/rustlings-jp):A Japanese translation of the Rustlings exercises. - πŸ‡¨πŸ‡³ [Simplified Chinese Rustlings](https://github.com/SandmeyerX/rustlings-zh-cn): A simplified Chinese translation of the Rustlings exercises. - πŸ‡ΊπŸ‡¦ [Rustlings in Ukrainian](https://github.com/noroutine/rustlings-ua): Translation of the Rustlings exercises in Ukrainian. +- πŸ‡°πŸ‡· [Korean Rustlings](https://github.com/eoncheole/rustlings-kr): A Korean translation of the Rustlings exercises. > You can use the same `rustlings` program that you installed with `cargo install rustlings` to run community exercises. diff --git a/website/content/setup/index.md b/website/content/setup/index.md index 54551ada..8147ff56 100644 --- a/website/content/setup/index.md +++ b/website/content/setup/index.md @@ -73,6 +73,10 @@ While working with Rustlings, please use a modern terminal for the best user exp The default terminal on Linux and Mac should be sufficient. On Windows, we recommend the [Windows Terminal](https://aka.ms/terminal). +### Offline documentation + +Whenever you're working on Rustlings offline, you can access a local copy of the standard library documentation by running `rustup doc --std`. + ## Usage After being done with the setup, visit the [**usage**](@/usage/index.md) page for some info about using Rustlings πŸš€ diff --git a/website/input.css b/website/input.css index af0675d8..01c956d6 100644 --- a/website/input.css +++ b/website/input.css @@ -41,14 +41,10 @@ @apply md:w-3/4 lg:w-3/5; } blockquote { - @apply px-3 pt-2 pb-0.5 mb-4 mt-2 border-s-4 border-white/80 bg-white/7 rounded-sm; + @apply px-3 pt-2 pb-px mb-4 mt-2 border-s-4 border-white/80 bg-white/7 rounded-sm; } pre { - @apply px-2 pt-2 pb-px overflow-x-auto text-sm sm:text-base rounded-sm mt-2 mb-4 after:content-[attr(data-lang)] after:text-[8px] after:opacity-40 selection:bg-white/15; - } - pre code mark { - @apply pb-0.5 pt-1 pr-px text-inherit rounded-xs; + @apply px-2 pt-2 pb-1.5 overflow-x-auto text-sm sm:text-base rounded-sm mt-2 mb-4 selection:bg-white/15; } } - diff --git a/website/package.json b/website/package.json index 38dd27e9..80c84872 100644 --- a/website/package.json +++ b/website/package.json @@ -1,5 +1,5 @@ { "dependencies": { - "@tailwindcss/cli": "^4.1" + "@tailwindcss/cli": "^4" } }