mirror of
https://github.com/rust-lang/rustlings.git
synced 2026-01-11 21:29: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"
|
name = "rustlings"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "fix-rust-analyzer"
|
|
||||||
path = "src/fix-rust-analyzer.rs"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
assert_cmd = "0.11.0"
|
assert_cmd = "0.11.0"
|
||||||
predicates = "1.0.1"
|
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::exercise::{Exercise, ExerciseList};
|
||||||
|
use crate::fix_rust_analyzer::{RustAnalyzerError, RustAnalyzerProject};
|
||||||
use crate::run::run;
|
use crate::run::run;
|
||||||
use crate::verify::verify;
|
use crate::verify::verify;
|
||||||
use argh::FromArgs;
|
use argh::FromArgs;
|
||||||
@ -20,6 +21,7 @@ use std::time::Duration;
|
|||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
mod exercise;
|
mod exercise;
|
||||||
|
mod fix_rust_analyzer;
|
||||||
mod run;
|
mod run;
|
||||||
mod verify;
|
mod verify;
|
||||||
|
|
||||||
@ -37,6 +39,9 @@ struct Args {
|
|||||||
version: bool,
|
version: bool,
|
||||||
#[argh(subcommand)]
|
#[argh(subcommand)]
|
||||||
nested: Option<Subcommands>,
|
nested: Option<Subcommands>,
|
||||||
|
/// skip rust-analyzer fix check
|
||||||
|
#[argh(switch, short = 'x')]
|
||||||
|
skipfix: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromArgs, PartialEq, Debug)]
|
#[derive(FromArgs, PartialEq, Debug)]
|
||||||
@ -128,6 +133,10 @@ fn main() {
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !Path::new("rust-project.json").exists() && !args.skipfix {
|
||||||
|
fix_rust_analyzer();
|
||||||
|
}
|
||||||
|
|
||||||
if !rustc_exists() {
|
if !rustc_exists() {
|
||||||
println!("We cannot find `rustc`.");
|
println!("We cannot find `rustc`.");
|
||||||
println!("Try running `rustc --version` to diagnose your problem.");
|
println!("Try running `rustc --version` to diagnose your problem.");
|
||||||
@ -404,3 +413,26 @@ fn rustc_exists() -> bool {
|
|||||||
.map(|status| status.success())
|
.map(|status| status.success())
|
||||||
.unwrap_or(false)
|
.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