mirror of
https://github.com/rust-lang/rustlings.git
synced 2026-01-11 13:19:18 +00:00
Move logic into main binary
This commit is contained in:
parent
f0bcb4a816
commit
a831aa7982
@ -21,10 +21,6 @@ home = "0.5.3"
|
||||
name = "rustlings"
|
||||
path = "src/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "fix-rust-analyzer"
|
||||
path = "src/fix-rust-analyzer.rs"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "0.11.0"
|
||||
predicates = "1.0.1"
|
||||
|
||||
@ -1,72 +0,0 @@
|
||||
use glob::glob;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct RustProject {
|
||||
sysroot_src: String,
|
||||
crates: Vec<Crate>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Crate {
|
||||
root_module: String,
|
||||
edition: String,
|
||||
deps: Vec<String>,
|
||||
}
|
||||
|
||||
impl RustProject {
|
||||
fn new() -> RustProject {
|
||||
RustProject {
|
||||
sysroot_src: RustProject::get_sysroot_src(),
|
||||
crates: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_sysroot_src() -> String {
|
||||
let mut sysroot_src = home::rustup_home()
|
||||
.expect("Can't find Rustup... aborting")
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
use std::process::Command;
|
||||
let output = Command::new("rustup")
|
||||
.arg("default")
|
||||
.output()
|
||||
.expect("Failed to get rustup default toolchain");
|
||||
|
||||
let toolchain = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
|
||||
sysroot_src += "/toolchains/";
|
||||
sysroot_src += toolchain
|
||||
.split_once(' ')
|
||||
.expect("Malformed default toolchain path")
|
||||
.0;
|
||||
sysroot_src += "/lib/rustlib/src/rust/library";
|
||||
println!("{}", sysroot_src);
|
||||
sysroot_src
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut project = RustProject::new();
|
||||
for e in glob("./exercises/**/*").expect("Glob failed to read pattern") {
|
||||
let path = e
|
||||
.expect("Unable to extract path")
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
if let Some((_, ext)) = path.split_once(".") {
|
||||
if ext == "rs" {
|
||||
project.crates.push(Crate {
|
||||
deps: Vec::new(),
|
||||
edition: "2021".to_string(),
|
||||
root_module: path,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
std::fs::write(
|
||||
"./rust-project.json",
|
||||
serde_json::to_vec(&project).expect("Failed to serialize to JSON"),
|
||||
)
|
||||
.expect("Failed to write file");
|
||||
}
|
||||
150
src/fix_rust_analyzer.rs
Normal file
150
src/fix_rust_analyzer.rs
Normal file
@ -0,0 +1,150 @@
|
||||
/// because `rustlings` is a special type of project where we don't have a
|
||||
/// cargo.toml linking to each exercise, we need a way to tell `rust-analyzer`
|
||||
/// how to parse the exercises. This functionality is built into rust-analyzer
|
||||
/// by putting a `rust-project.json` at the root of the repository. This module generates
|
||||
/// that file by finding the default toolchain used and looping through each exercise
|
||||
/// to build the configuration in a way that allows rust-analyzer to work with the exercises.
|
||||
use glob::glob;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::process::Command;
|
||||
|
||||
/// Custom error to check if io error or rust-analyzer just doesn't exist
|
||||
/// if rust-analyzer doesn't exist don't want to panic, want to print
|
||||
/// message to console and continue
|
||||
pub enum RustAnalyzerError {
|
||||
IoError(std::io::Error),
|
||||
NoRustAnalyzerError,
|
||||
}
|
||||
|
||||
impl fmt::Display for RustAnalyzerError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "invalid first item to double")
|
||||
}
|
||||
}
|
||||
|
||||
impl RustAnalyzerError {
|
||||
fn from_io(err: std::io::Error) -> RustAnalyzerError {
|
||||
RustAnalyzerError::IoError(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains the structure of resulting rust-project.json file
|
||||
/// and functions to build the data required to create the file
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct RustAnalyzerProject {
|
||||
sysroot_src: String,
|
||||
crates: Vec<Crate>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Crate {
|
||||
root_module: String,
|
||||
edition: String,
|
||||
deps: Vec<String>,
|
||||
}
|
||||
|
||||
impl RustAnalyzerProject {
|
||||
pub fn new() -> RustAnalyzerProject {
|
||||
RustAnalyzerProject {
|
||||
sysroot_src: String::new(),
|
||||
crates: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// If path contains .rs extension, add a crate to `rust-project.json`
|
||||
fn path_to_json(&mut self, path: String) {
|
||||
if let Some((_, ext)) = path.split_once(".") {
|
||||
if ext == "rs" {
|
||||
self.crates.push(Crate {
|
||||
root_module: path,
|
||||
edition: "2021".to_string(),
|
||||
deps: Vec::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Write rust-project.json to disk
|
||||
pub fn write_to_disk(&self) -> Result<(), std::io::Error> {
|
||||
std::fs::write(
|
||||
"./rust-project.json",
|
||||
serde_json::to_vec(&self).expect("Failed to serialize to JSON"),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse the exercises folder for .rs files, any matches will create
|
||||
/// a new `crate` in rust-project.json which allows rust-analyzer to
|
||||
/// treat it like a normal binary
|
||||
pub fn exercies_to_json(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
let glob = glob("./exercises/**/*")?;
|
||||
for e in glob {
|
||||
let path = e?.to_string_lossy().to_string();
|
||||
self.path_to_json(path);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run `rust-analyzer --version` to check if rust analyzer exists, if it doesn't
|
||||
/// then return custom error
|
||||
pub fn check_rust_analyzer_exists(&self) -> Result<(), RustAnalyzerError> {
|
||||
match Command::new("rust-analyzer").arg("--version").output() {
|
||||
Ok(out) => {
|
||||
if out.stderr.len() > 0 {
|
||||
return Err(RustAnalyzerError::NoRustAnalyzerError);
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(err) => Err(RustAnalyzerError::from_io(err)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Use `rustup` command to determine the default toolchain, if it exists
|
||||
/// it will be put in RustAnalyzerProject.sysroot_src, otherwise an error will be returned
|
||||
pub fn get_sysroot_src(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
let mut sysroot_src = home::rustup_home()?.to_string_lossy().to_string();
|
||||
|
||||
let output = Command::new("rustup").arg("default").output()?;
|
||||
|
||||
let toolchain = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
|
||||
sysroot_src += "/toolchains/";
|
||||
sysroot_src += toolchain
|
||||
.split_once(' ')
|
||||
.ok_or("Unable to determine toolchain")?
|
||||
.0;
|
||||
sysroot_src += "/lib/rustlib/src/rust/library";
|
||||
println!(
|
||||
"Determined toolchain to use with Rustlings: {}",
|
||||
sysroot_src
|
||||
);
|
||||
self.sysroot_src = sysroot_src;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_exercises() {
|
||||
let mut rust_project = RustAnalyzerProject::new();
|
||||
rust_project
|
||||
.exercies_to_json()
|
||||
.expect("Failed to parse exercises");
|
||||
assert_eq!(rust_project.crates.len() > 0, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_exists() {
|
||||
let rust_project = RustAnalyzerProject::new();
|
||||
match rust_project.check_rust_analyzer_exists() {
|
||||
Ok(_) => (),
|
||||
Err(err) => match err {
|
||||
RustAnalyzerError::NoRustAnalyzerError => {
|
||||
println!("Correctly identifying rust-analyzer doesn't exist")
|
||||
}
|
||||
RustAnalyzerError::IoError(err) => panic!("io error: {}", err),
|
||||
},
|
||||
}
|
||||
}
|
||||
32
src/main.rs
32
src/main.rs
@ -1,4 +1,5 @@
|
||||
use crate::exercise::{Exercise, ExerciseList};
|
||||
use crate::fix_rust_analyzer::{RustAnalyzerError, RustAnalyzerProject};
|
||||
use crate::run::run;
|
||||
use crate::verify::verify;
|
||||
use argh::FromArgs;
|
||||
@ -20,6 +21,7 @@ use std::time::Duration;
|
||||
mod ui;
|
||||
|
||||
mod exercise;
|
||||
mod fix_rust_analyzer;
|
||||
mod run;
|
||||
mod verify;
|
||||
|
||||
@ -37,6 +39,9 @@ struct Args {
|
||||
version: bool,
|
||||
#[argh(subcommand)]
|
||||
nested: Option<Subcommands>,
|
||||
/// skip rust-analyzer fix check
|
||||
#[argh(switch, short = 'x')]
|
||||
skipfix: bool,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
@ -128,6 +133,10 @@ fn main() {
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if !Path::new("rust-project.json").exists() && !args.skipfix {
|
||||
fix_rust_analyzer();
|
||||
}
|
||||
|
||||
if !rustc_exists() {
|
||||
println!("We cannot find `rustc`.");
|
||||
println!("Try running `rustc --version` to diagnose your problem.");
|
||||
@ -404,3 +413,26 @@ fn rustc_exists() -> bool {
|
||||
.map(|status| status.success())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn fix_rust_analyzer() {
|
||||
let mut rust_project = RustAnalyzerProject::new();
|
||||
if let Err(err) = rust_project.check_rust_analyzer_exists() {
|
||||
match err {
|
||||
RustAnalyzerError::NoRustAnalyzerError => {
|
||||
println!("rust-analyzer doesn't exist, skipping fix")
|
||||
}
|
||||
RustAnalyzerError::IoError(err) => {
|
||||
println!("error trying to find rust-analyzer: {}", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("rust-analyzer exists, fixing to work with rustlings");
|
||||
if let Err(err) = rust_project.get_sysroot_src() {
|
||||
println!("Error getting toolchain path: {}\nContinuing... rust-analyzer won't work with rustlings", &err)
|
||||
}
|
||||
if let Err(err) = rust_project.exercies_to_json() {
|
||||
println!("Error parsing exercises and converting to json: {}\nContinuing... rust-analyzer won't work with rustlings", &err)
|
||||
}
|
||||
rust_project.write_to_disk();
|
||||
}
|
||||
}
|
||||
|
||||
1
tests/fixture/failure/rust-project.json
Normal file
1
tests/fixture/failure/rust-project.json
Normal file
@ -0,0 +1 @@
|
||||
{"sysroot_src":"/home/jacko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library","crates":[]}
|
||||
1
tests/fixture/state/rust-project.json
Normal file
1
tests/fixture/state/rust-project.json
Normal file
@ -0,0 +1 @@
|
||||
{"sysroot_src":"/home/jacko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library","crates":[]}
|
||||
1
tests/fixture/success/rust-project.json
Normal file
1
tests/fixture/success/rust-project.json
Normal file
@ -0,0 +1 @@
|
||||
{"sysroot_src":"/home/jacko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library","crates":[]}
|
||||
Loading…
x
Reference in New Issue
Block a user