use std::env; use std::hash::Hash; use std::path::{Path, PathBuf}; use std::str::FromStr; use anyhow::{anyhow, bail, Result}; use clap::ValueEnum; use globset::{Glob, GlobSetBuilder}; use rustc_hash::FxHashSet; use serde::{de, Deserialize, Deserializer, Serialize}; use crate::checks::CheckCode; use crate::checks_gen::CheckCodePrefix; use crate::fs; #[derive(Clone, Copy, Debug, PartialOrd, PartialEq, Eq, Serialize, Deserialize, Hash)] #[serde(rename_all = "lowercase")] pub enum PythonVersion { Py33, Py34, Py35, Py36, Py37, Py38, Py39, Py310, Py311, } impl FromStr for PythonVersion { type Err = anyhow::Error; fn from_str(string: &str) -> Result { match string { "py33" => Ok(PythonVersion::Py33), "py34" => Ok(PythonVersion::Py34), "py35" => Ok(PythonVersion::Py35), "py36" => Ok(PythonVersion::Py36), "py37" => Ok(PythonVersion::Py37), "py38" => Ok(PythonVersion::Py38), "py39" => Ok(PythonVersion::Py39), "py310" => Ok(PythonVersion::Py310), "py311" => Ok(PythonVersion::Py311), _ => Err(anyhow!("Unknown version: {string}")), } } } #[derive(Debug, Clone)] pub enum FilePattern { Builtin(&'static str), User(String, PathBuf), } impl FilePattern { pub fn add_to(self, builder: &mut GlobSetBuilder) -> Result<()> { match self { FilePattern::Builtin(pattern) => { builder.add(Glob::from_str(pattern)?); } FilePattern::User(pattern, absolute) => { // Add the absolute path. builder.add(Glob::new(&absolute.to_string_lossy())?); // Add basename path. if !pattern.contains(std::path::MAIN_SEPARATOR) { builder.add(Glob::from_str(&pattern)?); } } } Ok(()) } } impl FromStr for FilePattern { type Err = anyhow::Error; fn from_str(s: &str) -> Result { let pattern = s.to_string(); let absolute = fs::normalize_path(Path::new(&pattern)); Ok(Self::User(pattern, absolute)) } } #[derive(Debug, Clone)] pub struct PerFileIgnore { pub basename: String, pub absolute: PathBuf, pub codes: FxHashSet, } impl PerFileIgnore { pub fn new(basename: String, absolute: PathBuf, prefixes: &[CheckCodePrefix]) -> Self { let codes = prefixes.iter().flat_map(CheckCodePrefix::codes).collect(); Self { basename, absolute, codes, } } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct PatternPrefixPair { pub pattern: String, pub prefix: CheckCodePrefix, } impl PatternPrefixPair { const EXPECTED_PATTERN: &'static str = ": pattern"; } impl<'de> Deserialize<'de> for PatternPrefixPair { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let str_result = String::deserialize(deserializer)?; Self::from_str(str_result.as_str()).map_err(|_| { de::Error::invalid_value( de::Unexpected::Str(str_result.as_str()), &Self::EXPECTED_PATTERN, ) }) } } impl FromStr for PatternPrefixPair { type Err = anyhow::Error; fn from_str(s: &str) -> Result { let (pattern_str, code_string) = { let tokens = s.split(':').collect::>(); if tokens.len() != 2 { bail!("Expected {}", Self::EXPECTED_PATTERN); } (tokens[0].trim(), tokens[1].trim()) }; let pattern = pattern_str.into(); let prefix = CheckCodePrefix::from_str(code_string)?; Ok(Self { pattern, prefix }) } } #[derive(Clone, Copy, ValueEnum, PartialEq, Eq, Serialize, Deserialize, Debug)] #[serde(rename_all = "kebab-case")] pub enum SerializationFormat { Text, Json, Junit, Grouped, Github, } impl Default for SerializationFormat { fn default() -> Self { if let Ok(github_actions) = env::var("GITHUB_ACTIONS") { if github_actions == "true" { return Self::Github; } } Self::Text } }