cargo_inspect/
lib.rs

1//! A library for inspecting Rust code
2#![warn(
3    missing_docs,
4    missing_debug_implementations,
5    missing_copy_implementations,
6    trivial_casts,
7    trivial_numeric_casts,
8    unsafe_code,
9    unstable_features,
10    unused_import_braces,
11    unused_qualifications
12)]
13
14use std::env;
15use std::fs;
16use std::fs::File;
17use std::io::Write;
18use std::path::PathBuf;
19use std::process::Command;
20
21/// Available configuration settings when using cargo-inspect as a library
22pub mod config;
23
24/// Contains all types defined for error handling
25pub mod errors;
26
27mod comment;
28mod diff;
29mod format;
30mod hir;
31mod tmpfile;
32
33use prettyprint::PrettyPrinter;
34
35pub use crate::config::{Config, Opt};
36pub use crate::errors::InspectError;
37
38use crate::comment::comment_file;
39pub use crate::diff::diff;
40use crate::format::format;
41use crate::hir::HIR;
42pub use crate::tmpfile::tmpfile;
43
44/// inspect takes a Rust file or crate as an input and returns the desugared
45/// output.
46pub fn inspect(config: &Config) -> Result<(), InspectError> {
47    let output = match &config.files {
48        Some(files) => {
49            let hir0 = inspect_file(
50                PathBuf::from(files.0.clone()),
51                config.verbose,
52                config.unpretty.clone(),
53            )?;
54            let hir1 = inspect_file(
55                PathBuf::from(files.1.clone()),
56                config.verbose,
57                config.unpretty.clone(),
58            )?;
59            diff(try_format(hir0.output)?, try_format(hir1.output)?)?
60        }
61        None => inspect_single(config)?,
62    };
63
64    if config.plain {
65        println!("{}", output);
66    } else if config.unpretty.starts_with("flowgraph") {
67        if let Some(path) = &config.input {
68            let input_path = PathBuf::from(path);
69
70            // Extract the file name from our input path.
71            let file_name = input_path.file_name().ok_or_else(|| {
72                InspectError::Flowgraph(String::from(
73                    "Invalid path found. The input path should be a file.",
74                ))
75            })?;
76
77            let mut output_path = PathBuf::from(file_name);
78            let ext = &config.format;
79            output_path.set_extension(&ext);
80
81            // Create a temporary file to dump out the plain output
82            let tmp_file_path = tmpfile()?;
83            let mut file = File::create(&tmp_file_path)?;
84            file.write_all(output.as_bytes())?;
85
86            // For now setup the correct `dot` arguments to write to a png
87            let output_str = output_path.to_str().ok_or_else(|| {
88                InspectError::Flowgraph(String::from("Failed to convert output path to string."))
89            })?;
90
91            let input_str = tmp_file_path.to_str().ok_or_else(|| {
92                InspectError::Flowgraph(String::from("Failed to convert temporary path to string."))
93            })?;
94
95            log::info!("Writing \"{}\"...", output_str);
96
97            let args = [&format!("-T{}", ext), "-o", output_str, input_str];
98            Command::new("dot")
99                .args(&args)
100                .spawn()
101                .map_err(|e| InspectError::DotExec(e))?;
102        }
103    } else {
104        let mut builder = PrettyPrinter::default();
105        builder.language("rust");
106        if let Some(theme) = &config.theme {
107            builder.theme(theme.clone());
108        }
109
110        let printer = builder.build()?;
111        let header = config.input.to_owned().unwrap_or(env::current_dir()?);
112        printer.string_with_header(output, header.to_string_lossy().to_string())?;
113    }
114    Ok(())
115}
116
117/// Run inspection on a single file or crate. Return the compiler output
118/// (preferably formatted with rustfmt)
119pub fn inspect_single(config: &Config) -> Result<String, InspectError> {
120    let hir = match config.input.clone() {
121        Some(input) => inspect_file(input, config.verbose, config.unpretty.clone()),
122        None => inspect_crate(config),
123    }?;
124    Ok(try_format(hir.output)?)
125}
126
127/// Run cargo-inspect on a file
128fn inspect_file(input: PathBuf, verbose: bool, unpretty: String) -> Result<HIR, InspectError> {
129    let input = if verbose {
130        // Create a temporary copy of the input file,
131        // which contains comments for each input line
132        // to avoid modifying the original input file.
133        // This will be used as the input of rustc.
134        let tmp = tmpfile()?;
135        fs::copy(&input, &tmp)?;
136        comment_file(&tmp)?;
137        tmp
138    } else {
139        input
140    };
141    hir::from_file(&input, &unpretty)
142}
143
144/// Run cargo-inspect on a crate
145fn inspect_crate(config: &Config) -> Result<HIR, InspectError> {
146    if config.verbose {
147        unimplemented!(
148            "Verbose option doesn't work for crates yet. \
149             See https://github.com/mre/cargo-inspect/issues/5"
150        )
151        // comment_crate()?;
152    }
153    hir::from_crate(&config.unpretty)
154}
155
156// TODO: This should really be more idiomatic;
157// maybe by having a `Formatted` type and a `let fmt = Formatted::try_from(string);`
158fn try_format(input: String) -> Result<String, InspectError> {
159    let mut formatted = format(&input)?;
160    if formatted.is_empty() {
161        // In case of an error, rustfmt returns an empty string
162        // and we continue with the unformatted output.
163        // Not ideal, but better than panicking.
164        formatted = input;
165    }
166    Ok(formatted)
167}