Skip to content

Commit 45a1a74

Browse files
authored
Merge pull request #1917 from mo8it/project
Rewrite `project.rs`
2 parents 864d046 + b24f256 commit 45a1a74

File tree

4 files changed

+75
-94
lines changed

4 files changed

+75
-94
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ authors = [
99
edition = "2021"
1010

1111
[dependencies]
12+
anyhow = "1.0.81"
1213
clap = { version = "4.5.2", features = ["derive"] }
1314
console = "0.15.8"
14-
glob = "0.3.0"
1515
indicatif = "0.17.8"
1616
notify-debouncer-mini = "0.4.1"
1717
serde_json = "1.0.114"

src/main.rs

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use crate::exercise::{Exercise, ExerciseList};
2-
use crate::project::RustAnalyzerProject;
2+
use crate::project::write_project_json;
33
use crate::run::{reset, run};
44
use crate::verify::verify;
5+
use anyhow::Result;
56
use clap::{Parser, Subcommand};
67
use console::Emoji;
78
use notify_debouncer_mini::notify::{self, RecursiveMode};
@@ -85,7 +86,7 @@ enum Subcommands {
8586
Lsp,
8687
}
8788

88-
fn main() {
89+
fn main() -> Result<()> {
8990
let args = Args::parse();
9091

9192
if args.command.is_none() {
@@ -218,18 +219,8 @@ fn main() {
218219
}
219220

220221
Subcommands::Lsp => {
221-
let mut project = RustAnalyzerProject::new();
222-
project
223-
.get_sysroot_src()
224-
.expect("Couldn't find toolchain path, do you have `rustc` installed?");
225-
project
226-
.exercises_to_json()
227-
.expect("Couldn't parse rustlings exercises files");
228-
229-
if project.crates.is_empty() {
230-
println!("Failed find any exercises, make sure you're in the `rustlings` folder");
231-
} else if project.write_to_disk().is_err() {
232-
println!("Failed to write rust-project.json to disk for rust-analyzer");
222+
if let Err(e) = write_project_json(exercises) {
223+
println!("Failed to write rust-project.json to disk for rust-analyzer: {e}");
233224
} else {
234225
println!("Successfully generated rust-project.json");
235226
println!("rust-analyzer will now parse exercises, restart your language server or editor");
@@ -255,6 +246,8 @@ fn main() {
255246
}
256247
},
257248
}
249+
250+
Ok(())
258251
}
259252

260253
fn spawn_watch_shell(

src/project.rs

Lines changed: 60 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,83 @@
1-
use glob::glob;
2-
use serde::{Deserialize, Serialize};
1+
use anyhow::{Context, Result};
2+
use serde::Serialize;
33
use std::env;
4-
use std::error::Error;
5-
use std::path::{Path, PathBuf};
6-
use std::process::Command;
4+
use std::path::PathBuf;
5+
use std::process::{Command, Stdio};
6+
7+
use crate::exercise::Exercise;
78

89
/// Contains the structure of resulting rust-project.json file
910
/// and functions to build the data required to create the file
10-
#[derive(Serialize, Deserialize)]
11-
pub struct RustAnalyzerProject {
12-
sysroot_src: String,
13-
pub crates: Vec<Crate>,
11+
#[derive(Serialize)]
12+
struct RustAnalyzerProject {
13+
sysroot_src: PathBuf,
14+
crates: Vec<Crate>,
1415
}
1516

16-
#[derive(Serialize, Deserialize)]
17-
pub struct Crate {
18-
root_module: String,
19-
edition: String,
20-
deps: Vec<String>,
21-
cfg: Vec<String>,
17+
#[derive(Serialize)]
18+
struct Crate {
19+
root_module: PathBuf,
20+
edition: &'static str,
21+
// Not used, but required in the JSON file.
22+
deps: Vec<()>,
23+
// Only `test` is used for all crates.
24+
// Therefore, an array is used instead of a `Vec`.
25+
cfg: [&'static str; 1],
2226
}
2327

2428
impl RustAnalyzerProject {
25-
pub fn new() -> RustAnalyzerProject {
26-
RustAnalyzerProject {
27-
sysroot_src: String::new(),
28-
crates: Vec::new(),
29-
}
30-
}
31-
32-
/// Write rust-project.json to disk
33-
pub fn write_to_disk(&self) -> Result<(), std::io::Error> {
34-
std::fs::write(
35-
"./rust-project.json",
36-
serde_json::to_vec(&self).expect("Failed to serialize to JSON"),
37-
)?;
38-
Ok(())
39-
}
29+
fn build(exercises: Vec<Exercise>) -> Result<Self> {
30+
let crates = exercises
31+
.into_iter()
32+
.map(|exercise| Crate {
33+
root_module: exercise.path,
34+
edition: "2021",
35+
deps: Vec::new(),
36+
// This allows rust_analyzer to work inside `#[test]` blocks
37+
cfg: ["test"],
38+
})
39+
.collect();
4040

41-
/// If path contains .rs extension, add a crate to `rust-project.json`
42-
fn path_to_json(&mut self, path: PathBuf) -> Result<(), Box<dyn Error>> {
43-
if let Some(ext) = path.extension() {
44-
if ext == "rs" {
45-
self.crates.push(Crate {
46-
root_module: path.display().to_string(),
47-
edition: "2021".to_string(),
48-
deps: Vec::new(),
49-
// This allows rust_analyzer to work inside #[test] blocks
50-
cfg: vec!["test".to_string()],
51-
})
52-
}
53-
}
54-
55-
Ok(())
56-
}
57-
58-
/// Parse the exercises folder for .rs files, any matches will create
59-
/// a new `crate` in rust-project.json which allows rust-analyzer to
60-
/// treat it like a normal binary
61-
pub fn exercises_to_json(&mut self) -> Result<(), Box<dyn Error>> {
62-
for path in glob("./exercises/**/*")? {
63-
self.path_to_json(path?)?;
64-
}
65-
Ok(())
66-
}
67-
68-
/// Use `rustc` to determine the default toolchain
69-
pub fn get_sysroot_src(&mut self) -> Result<(), Box<dyn Error>> {
70-
// check if RUST_SRC_PATH is set
71-
if let Ok(path) = env::var("RUST_SRC_PATH") {
72-
self.sysroot_src = path;
73-
return Ok(());
41+
if let Some(path) = env::var_os("RUST_SRC_PATH") {
42+
return Ok(Self {
43+
sysroot_src: PathBuf::from(path),
44+
crates,
45+
});
7446
}
7547

7648
let toolchain = Command::new("rustc")
7749
.arg("--print")
7850
.arg("sysroot")
79-
.output()?
51+
.stderr(Stdio::inherit())
52+
.output()
53+
.context("Failed to get the sysroot from `rustc`. Do you have `rustc` installed?")?
8054
.stdout;
8155

82-
let toolchain = String::from_utf8(toolchain)?;
56+
let toolchain =
57+
String::from_utf8(toolchain).context("The toolchain path is invalid UTF8")?;
8358
let toolchain = toolchain.trim_end();
84-
8559
println!("Determined toolchain: {toolchain}\n");
8660

87-
let Ok(path) = Path::new(toolchain)
88-
.join("lib")
89-
.join("rustlib")
90-
.join("src")
91-
.join("rust")
92-
.join("library")
93-
.into_os_string()
94-
.into_string()
95-
else {
96-
return Err("The sysroot path is invalid UTF8".into());
97-
};
98-
self.sysroot_src = path;
61+
let mut sysroot_src = PathBuf::with_capacity(256);
62+
sysroot_src.extend([toolchain, "lib", "rustlib", "src", "rust", "library"]);
9963

100-
Ok(())
64+
Ok(Self {
65+
sysroot_src,
66+
crates,
67+
})
10168
}
10269
}
70+
71+
/// Write `rust-project.json` to disk.
72+
pub fn write_project_json(exercises: Vec<Exercise>) -> Result<()> {
73+
let content = RustAnalyzerProject::build(exercises)?;
74+
75+
// Using the capacity 2^14 since the file length in bytes is higher than 2^13.
76+
// The final length is not known exactly because it depends on the user's sysroot path,
77+
// the current number of exercises etc.
78+
let mut buf = Vec::with_capacity(1 << 14);
79+
serde_json::to_writer(&mut buf, &content)?;
80+
std::fs::write("rust-project.json", buf)?;
81+
82+
Ok(())
83+
}

0 commit comments

Comments
 (0)