|
| 1 | +use std::path::{Path, PathBuf}; |
| 2 | +use std::process::{self, ExitStatus}; |
| 3 | +use std::{fs, io}; |
| 4 | + |
| 5 | +#[cfg(not(windows))] |
| 6 | +static CARGO_CLIPPY_EXE: &str = "cargo-clippy"; |
| 7 | +#[cfg(windows)] |
| 8 | +static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe"; |
| 9 | + |
| 10 | +/// Returns the path to the `cargo-clippy` binary |
| 11 | +/// |
| 12 | +/// # Panics |
| 13 | +/// |
| 14 | +/// Panics if the path of current executable could not be retrieved. |
| 15 | +#[must_use] |
| 16 | +pub fn cargo_clippy_path() -> PathBuf { |
| 17 | + let mut path = std::env::current_exe().expect("failed to get current executable name"); |
| 18 | + path.set_file_name(CARGO_CLIPPY_EXE); |
| 19 | + path |
| 20 | +} |
| 21 | + |
| 22 | +/// Returns the path to the Clippy project directory |
| 23 | +/// |
| 24 | +/// # Panics |
| 25 | +/// |
| 26 | +/// Panics if the current directory could not be retrieved, there was an error reading any of the |
| 27 | +/// Cargo.toml files or ancestor directory is the clippy root directory |
| 28 | +#[must_use] |
| 29 | +pub fn clippy_project_root() -> PathBuf { |
| 30 | + let current_dir = std::env::current_dir().unwrap(); |
| 31 | + for path in current_dir.ancestors() { |
| 32 | + let result = fs::read_to_string(path.join("Cargo.toml")); |
| 33 | + if let Err(err) = &result { |
| 34 | + if err.kind() == io::ErrorKind::NotFound { |
| 35 | + continue; |
| 36 | + } |
| 37 | + } |
| 38 | + |
| 39 | + let content = result.unwrap(); |
| 40 | + if content.contains("[package]\nname = \"clippy\"") { |
| 41 | + return path.to_path_buf(); |
| 42 | + } |
| 43 | + } |
| 44 | + panic!("error: Can't determine root of project. Please run inside a Clippy working dir."); |
| 45 | +} |
| 46 | + |
| 47 | +/// # Panics |
| 48 | +/// Panics if given command result was failed. |
| 49 | +pub fn exit_if_err(status: io::Result<ExitStatus>) { |
| 50 | + match status.expect("failed to run command").code() { |
| 51 | + Some(0) => {}, |
| 52 | + Some(n) => process::exit(n), |
| 53 | + None => { |
| 54 | + eprintln!("Killed by signal"); |
| 55 | + process::exit(1); |
| 56 | + }, |
| 57 | + } |
| 58 | +} |
| 59 | + |
| 60 | +pub(crate) fn clippy_version() -> (u32, u32) { |
| 61 | + fn parse_manifest(contents: &str) -> Option<(u32, u32)> { |
| 62 | + let version = contents |
| 63 | + .lines() |
| 64 | + .filter_map(|l| l.split_once('=')) |
| 65 | + .find_map(|(k, v)| (k.trim() == "version").then(|| v.trim()))?; |
| 66 | + let Some(("0", version)) = version.get(1..version.len() - 1)?.split_once('.') else { |
| 67 | + return None; |
| 68 | + }; |
| 69 | + let (minor, patch) = version.split_once('.')?; |
| 70 | + Some((minor.parse().ok()?, patch.parse().ok()?)) |
| 71 | + } |
| 72 | + let contents = fs::read_to_string("Cargo.toml").expect("Unable to read `Cargo.toml`"); |
| 73 | + parse_manifest(&contents).expect("Unable to find package version in `Cargo.toml`") |
| 74 | +} |
| 75 | + |
| 76 | +#[derive(Clone, Copy, PartialEq, Eq)] |
| 77 | +pub enum UpdateMode { |
| 78 | + Check, |
| 79 | + Change, |
| 80 | +} |
| 81 | + |
| 82 | +pub(crate) fn exit_with_failure() { |
| 83 | + println!( |
| 84 | + "Not all lints defined properly. \ |
| 85 | + Please run `cargo dev update_lints` to make sure all lints are defined properly." |
| 86 | + ); |
| 87 | + process::exit(1); |
| 88 | +} |
| 89 | + |
| 90 | +/// Replaces a region in a file delimited by two lines matching regexes. |
| 91 | +/// |
| 92 | +/// `path` is the relative path to the file on which you want to perform the replacement. |
| 93 | +/// |
| 94 | +/// See `replace_region_in_text` for documentation of the other options. |
| 95 | +/// |
| 96 | +/// # Panics |
| 97 | +/// |
| 98 | +/// Panics if the path could not read or then written |
| 99 | +pub(crate) fn replace_region_in_file( |
| 100 | + update_mode: UpdateMode, |
| 101 | + path: &Path, |
| 102 | + start: &str, |
| 103 | + end: &str, |
| 104 | + write_replacement: impl FnMut(&mut String), |
| 105 | +) { |
| 106 | + let contents = fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {e}", path.display())); |
| 107 | + let new_contents = match replace_region_in_text(&contents, start, end, write_replacement) { |
| 108 | + Ok(x) => x, |
| 109 | + Err(delim) => panic!("Couldn't find `{delim}` in file `{}`", path.display()), |
| 110 | + }; |
| 111 | + |
| 112 | + match update_mode { |
| 113 | + UpdateMode::Check if contents != new_contents => exit_with_failure(), |
| 114 | + UpdateMode::Check => (), |
| 115 | + UpdateMode::Change => { |
| 116 | + if let Err(e) = fs::write(path, new_contents.as_bytes()) { |
| 117 | + panic!("Cannot write to `{}`: {e}", path.display()); |
| 118 | + } |
| 119 | + }, |
| 120 | + } |
| 121 | +} |
| 122 | + |
| 123 | +/// Replaces a region in a text delimited by two strings. Returns the new text if both delimiters |
| 124 | +/// were found, or the missing delimiter if not. |
| 125 | +pub(crate) fn replace_region_in_text<'a>( |
| 126 | + text: &str, |
| 127 | + start: &'a str, |
| 128 | + end: &'a str, |
| 129 | + mut write_replacement: impl FnMut(&mut String), |
| 130 | +) -> Result<String, &'a str> { |
| 131 | + let (text_start, rest) = text.split_once(start).ok_or(start)?; |
| 132 | + let (_, text_end) = rest.split_once(end).ok_or(end)?; |
| 133 | + |
| 134 | + let mut res = String::with_capacity(text.len() + 4096); |
| 135 | + res.push_str(text_start); |
| 136 | + res.push_str(start); |
| 137 | + write_replacement(&mut res); |
| 138 | + res.push_str(end); |
| 139 | + res.push_str(text_end); |
| 140 | + |
| 141 | + Ok(res) |
| 142 | +} |
0 commit comments