mirror of
https://github.com/rust-lang/rustlings.git
synced 2024-12-28 00:00:03 +03:00
Compare commits
19 commits
4ce8667b9d
...
ce3dcc9856
Author | SHA1 | Date | |
---|---|---|---|
ce3dcc9856 | |||
4472d50eba | |||
a1d5702ba0 | |||
52a231ce2f | |||
16af981772 | |||
fc141b8dfc | |||
82ebd29ff6 | |||
f5737b5a49 | |||
55e68d2c63 | |||
479f45da9b | |||
140c4e4812 | |||
337460d299 | |||
e41c3a7c92 | |||
1b9faa4d61 | |||
9f9a754a64 | |||
f7b0cfe8d1 | |||
8b43d79257 | |||
dc086c6bf1 | |||
dc0ffbe16e |
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -1,13 +1,20 @@
|
||||||
<a name="6.2.0"></a>
|
<a name="6.2.0"></a>
|
||||||
|
|
||||||
## 6.2.0 (2024-08-08)
|
## 6.2.0 (2024-08-09)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Show a message before checking and running an exercise. This gives the user instant feedback and avoids confusion if the checks take too long.
|
||||||
- Show a helpful error message when trying to install Rustlings with a Rust version lower than the minimum one that Rustlings supports.
|
- Show a helpful error message when trying to install Rustlings with a Rust version lower than the minimum one that Rustlings supports.
|
||||||
- Remove the state file and the solutions directory from the generated `.gitignore` file.
|
|
||||||
- Add a `README.md` file to the `solutions/` directory.
|
- Add a `README.md` file to the `solutions/` directory.
|
||||||
|
- Allow initializing Rustlings in a Cargo workspace.
|
||||||
|
- `dev check`: Check that all solutions are formatted with `rustfmt`.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Remove the state file and the solutions directory from the generated `.gitignore` file.
|
||||||
- Run the final check of all exercises in parallel.
|
- Run the final check of all exercises in parallel.
|
||||||
- Small exercise improvements.
|
- Small exercise improvements.
|
||||||
- `dev check`: Check that all solutions are formatted with `rustfmt`.
|
|
||||||
|
|
||||||
<a name="6.1.0"></a>
|
<a name="6.1.0"></a>
|
||||||
|
|
||||||
|
|
135
Cargo.lock
generated
135
Cargo.lock
generated
|
@ -116,9 +116,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.13"
|
version = "4.5.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc"
|
checksum = "c937d4061031a6d0c8da4b9a4f98a172fc2976dfb1c19213a9cf7d0d3c837e36"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
|
@ -126,9 +126,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.13"
|
version = "4.5.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99"
|
checksum = "85379ba512b21a328adf887e85f7742d12e96eb31f3ef077df4ffc26b506ffed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
|
@ -162,13 +162,14 @@ checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "compact_str"
|
name = "compact_str"
|
||||||
version = "0.7.1"
|
version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f"
|
checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"castaway",
|
"castaway",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"itoa",
|
"itoa",
|
||||||
|
"rustversion",
|
||||||
"ryu",
|
"ryu",
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
]
|
]
|
||||||
|
@ -190,15 +191,15 @@ checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossterm"
|
name = "crossterm"
|
||||||
version = "0.27.0"
|
version = "0.28.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
|
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
"crossterm_winapi",
|
"crossterm_winapi",
|
||||||
"libc",
|
"mio 1.0.1",
|
||||||
"mio",
|
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
"rustix",
|
||||||
"signal-hook",
|
"signal-hook",
|
||||||
"signal-hook-mio",
|
"signal-hook-mio",
|
||||||
"winapi",
|
"winapi",
|
||||||
|
@ -225,6 +226,22 @@ version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastrand"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filetime"
|
name = "filetime"
|
||||||
version = "0.2.23"
|
version = "0.2.23"
|
||||||
|
@ -262,6 +279,12 @@ version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
|
@ -292,6 +315,16 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "instability"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is_terminal_polyfill"
|
name = "is_terminal_polyfill"
|
||||||
version = "1.70.1"
|
version = "1.70.1"
|
||||||
|
@ -339,6 +372,12 @@ version = "0.2.155"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.4.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.12"
|
version = "0.4.12"
|
||||||
|
@ -382,6 +421,19 @@ dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"wasi",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "notify"
|
name = "notify"
|
||||||
version = "6.1.1"
|
version = "6.1.1"
|
||||||
|
@ -396,7 +448,7 @@ dependencies = [
|
||||||
"kqueue",
|
"kqueue",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"mio",
|
"mio 0.8.11",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
@ -476,18 +528,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ratatui"
|
name = "ratatui"
|
||||||
version = "0.27.0"
|
version = "0.28.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d16546c5b5962abf8ce6e2881e722b4e0ae3b6f1a08a26ae3573c55853ca68d3"
|
checksum = "5ba6a365afbe5615999275bea2446b970b10a41102500e27ce7678d50d978303"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
"cassowary",
|
"cassowary",
|
||||||
"compact_str",
|
"compact_str",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
|
"instability",
|
||||||
"itertools",
|
"itertools",
|
||||||
"lru",
|
"lru",
|
||||||
"paste",
|
"paste",
|
||||||
"stability",
|
|
||||||
"strum",
|
"strum",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
|
@ -514,24 +566,38 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustlings"
|
name = "rustix"
|
||||||
version = "6.1.0"
|
version = "0.38.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bitflags 2.6.0",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustlings"
|
||||||
|
version = "6.2.0"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
"hashbrown",
|
|
||||||
"notify-debouncer-mini",
|
"notify-debouncer-mini",
|
||||||
"os_pipe",
|
"os_pipe",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"rustlings-macros",
|
"rustlings-macros",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"tempfile",
|
||||||
"toml_edit",
|
"toml_edit",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustlings-macros"
|
name = "rustlings-macros"
|
||||||
version = "6.1.0"
|
version = "6.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -567,18 +633,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.204"
|
version = "1.0.205"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
|
checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.204"
|
version = "1.0.205"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
|
checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -623,7 +689,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
|
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio 1.0.1",
|
||||||
"signal-hook",
|
"signal-hook",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -642,16 +708,6 @@ version = "1.13.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "stability"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac"
|
|
||||||
dependencies = [
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "static_assertions"
|
name = "static_assertions"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -697,6 +753,19 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tempfile"
|
||||||
|
version = "3.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"fastrand",
|
||||||
|
"once_cell",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.6.8"
|
version = "0.6.8"
|
||||||
|
|
15
Cargo.toml
15
Cargo.toml
|
@ -6,7 +6,7 @@ exclude = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "6.1.0"
|
version = "6.2.0"
|
||||||
authors = [
|
authors = [
|
||||||
"Mo Bitar <mo8it@proton.me>", # https://github.com/mo8it
|
"Mo Bitar <mo8it@proton.me>", # https://github.com/mo8it
|
||||||
"Liv <mokou@fastmail.com>", # https://github.com/shadows-withal
|
"Liv <mokou@fastmail.com>", # https://github.com/shadows-withal
|
||||||
|
@ -19,7 +19,7 @@ edition = "2021" # On Update: Update the edition of the `rustfmt` command that c
|
||||||
rust-version = "1.80"
|
rust-version = "1.80"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
serde = { version = "1.0.204", features = ["derive"] }
|
serde = { version = "1.0.205", features = ["derive"] }
|
||||||
toml_edit = { version = "0.22.20", default-features = false, features = ["parse", "serde"] }
|
toml_edit = { version = "0.22.20", default-features = false, features = ["parse", "serde"] }
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
|
@ -46,17 +46,20 @@ include = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
ahash = { version = "0.8.11", default-features = false }
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
clap = { version = "4.5.13", features = ["derive"] }
|
clap = { version = "4.5.14", features = ["derive"] }
|
||||||
hashbrown = "0.14.5"
|
|
||||||
notify-debouncer-mini = { version = "0.4.1", default-features = false }
|
notify-debouncer-mini = { version = "0.4.1", default-features = false }
|
||||||
os_pipe = "1.2.1"
|
os_pipe = "1.2.1"
|
||||||
ratatui = { version = "0.27.0", default-features = false, features = ["crossterm"] }
|
ratatui = { version = "0.28.0", default-features = false, features = ["crossterm"] }
|
||||||
rustlings-macros = { path = "rustlings-macros", version = "=6.1.0" }
|
rustlings-macros = { path = "rustlings-macros", version = "=6.2.0" }
|
||||||
serde_json = "1.0.122"
|
serde_json = "1.0.122"
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
toml_edit.workspace = true
|
toml_edit.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempfile = "3.12.0"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
|
||||||
|
|
|
@ -3,4 +3,4 @@
|
||||||
Before you finish an exercise, its solution file will only contain an empty `main` function.
|
Before you finish an exercise, its solution file will only contain an empty `main` function.
|
||||||
The content of this file will be automatically replaced by the actual solution once you finish the exercise.
|
The content of this file will be automatically replaced by the actual solution once you finish the exercise.
|
||||||
|
|
||||||
Note that these solution are often only _one possibility_ to solve an exercise.
|
Note that these solutions are often only _one possibility_ to solve an exercise.
|
||||||
|
|
|
@ -10,6 +10,7 @@ use std::{
|
||||||
use crate::{
|
use crate::{
|
||||||
clear_terminal,
|
clear_terminal,
|
||||||
cmd::CmdRunner,
|
cmd::CmdRunner,
|
||||||
|
collections::hash_set_with_capacity,
|
||||||
embedded::EMBEDDED_FILES,
|
embedded::EMBEDDED_FILES,
|
||||||
exercise::{Exercise, RunnableExercise},
|
exercise::{Exercise, RunnableExercise},
|
||||||
info_file::ExerciseInfo,
|
info_file::ExerciseInfo,
|
||||||
|
@ -69,7 +70,7 @@ impl AppState {
|
||||||
return StateFileStatus::NotRead;
|
return StateFileStatus::NotRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut done_exercises = hashbrown::HashSet::with_capacity(self.exercises.len());
|
let mut done_exercises = hash_set_with_capacity(self.exercises.len());
|
||||||
|
|
||||||
for done_exerise_name in lines {
|
for done_exerise_name in lines {
|
||||||
if done_exerise_name.is_empty() {
|
if done_exerise_name.is_empty() {
|
||||||
|
|
16
src/cmd.rs
16
src/cmd.rs
|
@ -1,4 +1,4 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{
|
use std::{
|
||||||
io::Read,
|
io::Read,
|
||||||
|
@ -68,12 +68,16 @@ impl CmdRunner {
|
||||||
.stdin(Stdio::null())
|
.stdin(Stdio::null())
|
||||||
.stderr(Stdio::inherit())
|
.stderr(Stdio::inherit())
|
||||||
.output()
|
.output()
|
||||||
.context(CARGO_METADATA_ERR)?
|
.context(CARGO_METADATA_ERR)?;
|
||||||
.stdout;
|
|
||||||
|
|
||||||
let target_dir = serde_json::de::from_slice::<CargoMetadata>(&metadata_output)
|
if !metadata_output.status.success() {
|
||||||
.context("Failed to read the field `target_directory` from the `cargo metadata` output")
|
bail!("The command `cargo metadata …` failed. Are you in the `rustlings/` directory?");
|
||||||
.map(|metadata| metadata.target_directory)?;
|
}
|
||||||
|
|
||||||
|
let target_dir = serde_json::de::from_slice::<CargoMetadata>(&metadata_output.stdout)
|
||||||
|
.context(
|
||||||
|
"Failed to read the field `target_directory` from the output of the command `cargo metadata …`",
|
||||||
|
)?.target_directory;
|
||||||
|
|
||||||
Ok(Self { target_dir })
|
Ok(Self { target_dir })
|
||||||
}
|
}
|
||||||
|
|
10
src/collections.rs
Normal file
10
src/collections.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
use ahash::AHasher;
|
||||||
|
use std::hash::BuildHasherDefault;
|
||||||
|
|
||||||
|
/// DOS attacks aren't a concern for Rustlings. Therefore, we use `ahash` with fixed seeds.
|
||||||
|
pub type HashSet<T> = std::collections::HashSet<T, BuildHasherDefault<AHasher>>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn hash_set_with_capacity<T>(capacity: usize) -> HashSet<T> {
|
||||||
|
HashSet::with_capacity_and_hasher(capacity, BuildHasherDefault::<AHasher>::default())
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ use std::{
|
||||||
use crate::{
|
use crate::{
|
||||||
cargo_toml::{append_bins, bins_start_end_ind, BINS_BUFFER_CAPACITY},
|
cargo_toml::{append_bins, bins_start_end_ind, BINS_BUFFER_CAPACITY},
|
||||||
cmd::CmdRunner,
|
cmd::CmdRunner,
|
||||||
|
collections::{hash_set_with_capacity, HashSet},
|
||||||
exercise::{RunnableExercise, OUTPUT_CAPACITY},
|
exercise::{RunnableExercise, OUTPUT_CAPACITY},
|
||||||
info_file::{ExerciseInfo, InfoFile},
|
info_file::{ExerciseInfo, InfoFile},
|
||||||
CURRENT_FORMAT_VERSION,
|
CURRENT_FORMAT_VERSION,
|
||||||
|
@ -48,9 +49,9 @@ fn check_cargo_toml(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the info of all exercises and return their paths in a set.
|
// Check the info of all exercises and return their paths in a set.
|
||||||
fn check_info_file_exercises(info_file: &InfoFile) -> Result<hashbrown::HashSet<PathBuf>> {
|
fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
|
||||||
let mut names = hashbrown::HashSet::with_capacity(info_file.exercises.len());
|
let mut names = hash_set_with_capacity(info_file.exercises.len());
|
||||||
let mut paths = hashbrown::HashSet::with_capacity(info_file.exercises.len());
|
let mut paths = hash_set_with_capacity(info_file.exercises.len());
|
||||||
|
|
||||||
let mut file_buf = String::with_capacity(1 << 14);
|
let mut file_buf = String::with_capacity(1 << 14);
|
||||||
for exercise_info in &info_file.exercises {
|
for exercise_info in &info_file.exercises {
|
||||||
|
@ -111,10 +112,7 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result<hashbrown::HashSet<
|
||||||
// Check `dir` for unexpected files.
|
// Check `dir` for unexpected files.
|
||||||
// Only Rust files in `allowed_rust_files` and `README.md` files are allowed.
|
// Only Rust files in `allowed_rust_files` and `README.md` files are allowed.
|
||||||
// Only one level of directory nesting is allowed.
|
// Only one level of directory nesting is allowed.
|
||||||
fn check_unexpected_files(
|
fn check_unexpected_files(dir: &str, allowed_rust_files: &HashSet<PathBuf>) -> Result<()> {
|
||||||
dir: &str,
|
|
||||||
allowed_rust_files: &hashbrown::HashSet<PathBuf>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let unexpected_file = |path: &Path| {
|
let unexpected_file = |path: &Path| {
|
||||||
anyhow!("Found the file `{}`. Only `README.md` and Rust files related to an exercise in `info.toml` are allowed in the `{dir}` directory", path.display())
|
anyhow!("Found the file `{}`. Only `README.md` and Rust files related to an exercise in `info.toml` are allowed in the `{dir}` directory", path.display())
|
||||||
};
|
};
|
||||||
|
@ -253,7 +251,7 @@ fn check_solutions(
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let mut sol_paths = hashbrown::HashSet::with_capacity(info_file.exercises.len());
|
let mut sol_paths = hash_set_with_capacity(info_file.exercises.len());
|
||||||
let mut fmt_cmd = Command::new("rustfmt");
|
let mut fmt_cmd = Command::new("rustfmt");
|
||||||
fmt_cmd
|
fmt_cmd
|
||||||
.arg("--check")
|
.arg("--check")
|
||||||
|
|
85
src/init.rs
85
src/init.rs
|
@ -1,10 +1,11 @@
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use ratatui::crossterm::style::Stylize;
|
use ratatui::crossterm::style::Stylize;
|
||||||
|
use serde::Deserialize;
|
||||||
use std::{
|
use std::{
|
||||||
env::set_current_dir,
|
env::set_current_dir,
|
||||||
fs::{self, create_dir},
|
fs::{self, create_dir},
|
||||||
io::{self, Write},
|
io::{self, Write},
|
||||||
path::Path,
|
path::{Path, PathBuf},
|
||||||
process::{Command, Stdio},
|
process::{Command, Stdio},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -13,27 +14,75 @@ use crate::{
|
||||||
term::press_enter_prompt,
|
term::press_enter_prompt,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct CargoLocateProject {
|
||||||
|
root: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn init() -> Result<()> {
|
pub fn init() -> Result<()> {
|
||||||
let rustlings_dir = Path::new("rustlings");
|
let rustlings_dir = Path::new("rustlings");
|
||||||
if rustlings_dir.exists() {
|
if rustlings_dir.exists() {
|
||||||
bail!(RUSTLINGS_DIR_ALREADY_EXISTS_ERR);
|
bail!(RUSTLINGS_DIR_ALREADY_EXISTS_ERR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let locate_project_output = Command::new("cargo")
|
||||||
|
.arg("locate-project")
|
||||||
|
.arg("-q")
|
||||||
|
.arg("--workspace")
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.output()
|
||||||
|
.context(CARGO_LOCATE_PROJECT_ERR)?;
|
||||||
|
|
||||||
let mut stdout = io::stdout().lock();
|
let mut stdout = io::stdout().lock();
|
||||||
let mut init_git = true;
|
let mut init_git = true;
|
||||||
|
|
||||||
if Path::new("Cargo.toml").exists() {
|
if locate_project_output.status.success() {
|
||||||
if Path::new("exercises").exists() && Path::new("solutions").exists() {
|
if Path::new("exercises").exists() && Path::new("solutions").exists() {
|
||||||
bail!(IN_INITIALIZED_DIR_ERR);
|
bail!(IN_INITIALIZED_DIR_ERR);
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout.write_all(CARGO_TOML_EXISTS_PROMPT_MSG)?;
|
let workspace_manifest =
|
||||||
press_enter_prompt(&mut stdout)?;
|
serde_json::de::from_slice::<CargoLocateProject>(&locate_project_output.stdout)
|
||||||
init_git = false;
|
.context(
|
||||||
}
|
"Failed to read the field `root` from the output of `cargo locate-project …`",
|
||||||
|
)?
|
||||||
|
.root;
|
||||||
|
|
||||||
stdout.write_all(b"This command will create the directory `rustlings/` which will contain the exercises.\nPress ENTER to continue ")?;
|
let workspace_manifest_content = fs::read_to_string(&workspace_manifest)
|
||||||
press_enter_prompt(&mut stdout)?;
|
.with_context(|| format!("Failed to read the file {}", workspace_manifest.display()))?;
|
||||||
|
if !workspace_manifest_content.contains("[workspace]\n")
|
||||||
|
&& !workspace_manifest_content.contains("workspace.")
|
||||||
|
{
|
||||||
|
bail!("The current directory is already part of a Cargo project.\nPlease initialize Rustlings in a different directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout.write_all(b"This command will create the directory `rustlings/` as a member of this Cargo workspace.\nPress ENTER to continue ")?;
|
||||||
|
press_enter_prompt(&mut stdout)?;
|
||||||
|
|
||||||
|
// Make sure "rustlings" is added to `workspace.members` by making
|
||||||
|
// Cargo initialize a new project.
|
||||||
|
let status = Command::new("cargo")
|
||||||
|
.arg("new")
|
||||||
|
.arg("-q")
|
||||||
|
.arg("--vcs")
|
||||||
|
.arg("none")
|
||||||
|
.arg("rustlings")
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.status()?;
|
||||||
|
if !status.success() {
|
||||||
|
bail!("Failed to initialize a new Cargo workspace member.\nPlease initialize Rustlings in a different directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
.context("Failed to remove the temporary directory `rustlings/`")?;
|
||||||
|
init_git = false;
|
||||||
|
} else {
|
||||||
|
stdout.write_all(b"This command will create the directory `rustlings/` which will contain the exercises.\nPress ENTER to continue ")?;
|
||||||
|
press_enter_prompt(&mut stdout)?;
|
||||||
|
}
|
||||||
|
|
||||||
create_dir(rustlings_dir).context("Failed to create the `rustlings/` directory")?;
|
create_dir(rustlings_dir).context("Failed to create the `rustlings/` directory")?;
|
||||||
set_current_dir(rustlings_dir)
|
set_current_dir(rustlings_dir)
|
||||||
|
@ -104,6 +153,10 @@ pub fn init() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CARGO_LOCATE_PROJECT_ERR: &str = "Failed to run the command `cargo locate-project …`
|
||||||
|
Did you already install Rust?
|
||||||
|
Try running `cargo --version` to diagnose the problem.";
|
||||||
|
|
||||||
const INIT_SOLUTION_FILE: &[u8] = b"fn main() {
|
const INIT_SOLUTION_FILE: &[u8] = b"fn main() {
|
||||||
// DON'T EDIT THIS SOLUTION FILE!
|
// DON'T EDIT THIS SOLUTION FILE!
|
||||||
// It will be automatically filled after you finish the exercise.
|
// It will be automatically filled after you finish the exercise.
|
||||||
|
@ -120,7 +173,7 @@ pub const VS_CODE_EXTENSIONS_JSON: &[u8] = br#"{"recommendations":["rust-lang.ru
|
||||||
const IN_INITIALIZED_DIR_ERR: &str = "It looks like Rustlings is already initialized in this directory.
|
const IN_INITIALIZED_DIR_ERR: &str = "It looks like Rustlings is already initialized in this directory.
|
||||||
|
|
||||||
If you already initialized Rustlings, run the command `rustlings` for instructions on getting started with the exercises.
|
If you already initialized Rustlings, run the command `rustlings` for instructions on getting started with the exercises.
|
||||||
Otherwise, please run `rustlings init` again in another directory.";
|
Otherwise, please run `rustlings init` again in a different directory.";
|
||||||
|
|
||||||
const RUSTLINGS_DIR_ALREADY_EXISTS_ERR: &str =
|
const RUSTLINGS_DIR_ALREADY_EXISTS_ERR: &str =
|
||||||
"A directory with the name `rustlings` already exists in the current directory.
|
"A directory with the name `rustlings` already exists in the current directory.
|
||||||
|
@ -128,19 +181,5 @@ You probably already initialized Rustlings.
|
||||||
Run `cd rustlings`
|
Run `cd rustlings`
|
||||||
Then run `rustlings` again";
|
Then run `rustlings` again";
|
||||||
|
|
||||||
const CARGO_TOML_EXISTS_PROMPT_MSG: &[u8] = br#"You are about to initialize Rustlings in a directory that already contains a `Cargo.toml` file!
|
|
||||||
|
|
||||||
=> It is recommended to abort with CTRL+C and initialize Rustlings in another directory <=
|
|
||||||
|
|
||||||
If you know what you are doing and want to initialize Rustlings in a Cargo workspace,
|
|
||||||
then you need to add its directory to `members` in the `workspace` section of the `Cargo.toml` file:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[workspace]
|
|
||||||
members = ["rustlings"]
|
|
||||||
```
|
|
||||||
|
|
||||||
Press ENTER if you are sure that you want to continue after reading the warning above "#;
|
|
||||||
|
|
||||||
const POST_INIT_MSG: &str = "Run `cd rustlings` to go into the generated directory.
|
const POST_INIT_MSG: &str = "Run `cd rustlings` to go into the generated directory.
|
||||||
Then run `rustlings` to get started.";
|
Then run `rustlings` to get started.";
|
||||||
|
|
|
@ -27,7 +27,7 @@ pub fn list(app_state: &mut AppState) -> Result<()> {
|
||||||
let mut ui_state = UiState::new(app_state);
|
let mut ui_state = UiState::new(app_state);
|
||||||
|
|
||||||
'outer: loop {
|
'outer: loop {
|
||||||
terminal.draw(|frame| ui_state.draw(frame).unwrap())?;
|
terminal.try_draw(|frame| ui_state.draw(frame).map_err(io::Error::other))?;
|
||||||
|
|
||||||
let key = loop {
|
let key = loop {
|
||||||
match event::read()? {
|
match event::read()? {
|
||||||
|
|
|
@ -161,7 +161,7 @@ impl<'a> UiState<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&mut self, frame: &mut Frame) -> Result<()> {
|
pub fn draw(&mut self, frame: &mut Frame) -> Result<()> {
|
||||||
let area = frame.size();
|
let area = frame.area();
|
||||||
|
|
||||||
frame.render_stateful_widget(
|
frame.render_stateful_widget(
|
||||||
&self.table,
|
&self.table,
|
||||||
|
|
|
@ -13,6 +13,7 @@ use self::{app_state::AppState, dev::DevCommands, info_file::InfoFile, watch::Wa
|
||||||
mod app_state;
|
mod app_state;
|
||||||
mod cargo_toml;
|
mod cargo_toml;
|
||||||
mod cmd;
|
mod cmd;
|
||||||
|
mod collections;
|
||||||
mod dev;
|
mod dev;
|
||||||
mod embedded;
|
mod embedded;
|
||||||
mod exercise;
|
mod exercise;
|
||||||
|
@ -105,7 +106,7 @@ fn main() -> Result<()> {
|
||||||
write!(stdout, "{welcome_message}\n\nPress ENTER to continue ")?;
|
write!(stdout, "{welcome_message}\n\nPress ENTER to continue ")?;
|
||||||
press_enter_prompt(&mut stdout)?;
|
press_enter_prompt(&mut stdout)?;
|
||||||
clear_terminal(&mut stdout)?;
|
clear_terminal(&mut stdout)?;
|
||||||
// Flush to be able to show errors occuring before printing a newline to stdout.
|
// Flush to be able to show errors occurring before printing a newline to stdout.
|
||||||
stdout.flush()?;
|
stdout.flush()?;
|
||||||
}
|
}
|
||||||
StateFileStatus::Read => (),
|
StateFileStatus::Read => (),
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
env::{self, consts::EXE_SUFFIX},
|
env::{self, consts::EXE_SUFFIX},
|
||||||
fs,
|
|
||||||
process::{Command, Stdio},
|
process::{Command, Stdio},
|
||||||
str::from_utf8,
|
str::from_utf8,
|
||||||
};
|
};
|
||||||
|
@ -155,28 +154,29 @@ fn hint() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn init() {
|
fn init() {
|
||||||
let _ = fs::remove_dir_all("tests/rustlings");
|
let test_dir = tempfile::TempDir::new().unwrap();
|
||||||
|
let test_dir = test_dir.path().to_str().unwrap();
|
||||||
|
|
||||||
Cmd::default().current_dir("tests").fail();
|
Cmd::default().current_dir(test_dir).fail();
|
||||||
|
|
||||||
Cmd::default()
|
Cmd::default()
|
||||||
.current_dir("tests")
|
.current_dir(test_dir)
|
||||||
.args(&["init"])
|
.args(&["init"])
|
||||||
.success();
|
.success();
|
||||||
|
|
||||||
// Running `init` after a successful initialization.
|
// Running `init` after a successful initialization.
|
||||||
Cmd::default()
|
Cmd::default()
|
||||||
.current_dir("tests")
|
.current_dir(test_dir)
|
||||||
.args(&["init"])
|
.args(&["init"])
|
||||||
.output(PartialStderr("`cd rustlings`"))
|
.output(PartialStderr("`cd rustlings`"))
|
||||||
.fail();
|
.fail();
|
||||||
|
|
||||||
|
let initialized_dir = format!("{test_dir}/rustlings");
|
||||||
|
|
||||||
// Running `init` in the initialized directory.
|
// Running `init` in the initialized directory.
|
||||||
Cmd::default()
|
Cmd::default()
|
||||||
.current_dir("tests/rustlings")
|
.current_dir(&initialized_dir)
|
||||||
.args(&["init"])
|
.args(&["init"])
|
||||||
.output(PartialStderr("already initialized"))
|
.output(PartialStderr("already initialized"))
|
||||||
.fail();
|
.fail();
|
||||||
|
|
||||||
fs::remove_dir_all("tests/rustlings").unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue