Skip to main content

uv_python/
implementation.rs

1use std::{
2    fmt::{self, Display},
3    str::FromStr,
4};
5use thiserror::Error;
6
7use crate::Interpreter;
8
9#[derive(Error, Debug)]
10pub enum Error {
11    #[error("Unknown Python implementation `{0}`")]
12    UnknownImplementation(String),
13}
14
15#[derive(Debug, Eq, PartialEq, Clone, Copy, Default, PartialOrd, Ord, Hash, serde::Serialize)]
16#[serde(rename_all = "lowercase")]
17pub enum ImplementationName {
18    Pyodide,
19    GraalPy,
20    PyPy,
21    #[default]
22    CPython,
23}
24
25#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash, serde::Serialize)]
26#[serde(untagged)]
27pub enum LenientImplementationName {
28    Unknown(String),
29    Known(ImplementationName),
30}
31
32impl ImplementationName {
33    /// Return the full implementation name.
34    pub const fn long_name(self) -> &'static str {
35        match self {
36            Self::CPython => "cpython",
37            Self::PyPy => "pypy",
38            Self::GraalPy => "graalpy",
39            Self::Pyodide => "pyodide",
40        }
41    }
42
43    /// Return the abbreviated implementation name, if one exists.
44    pub const fn short_name(self) -> Option<&'static str> {
45        match self {
46            Self::CPython => Some("cp"),
47            Self::PyPy => Some("pp"),
48            Self::GraalPy => Some("gp"),
49            Self::Pyodide => None,
50        }
51    }
52
53    pub(crate) fn iter_all() -> impl Iterator<Item = Self> {
54        [Self::CPython, Self::PyPy, Self::GraalPy, Self::Pyodide].into_iter()
55    }
56
57    pub(crate) fn pretty(self) -> &'static str {
58        match self {
59            Self::CPython => "CPython",
60            Self::PyPy => "PyPy",
61            Self::GraalPy => "GraalPy",
62            Self::Pyodide => "Pyodide",
63        }
64    }
65
66    /// The executable name used in distributions of this implementation.
67    pub(crate) fn executable_name(self) -> &'static str {
68        match self {
69            Self::CPython | Self::Pyodide => "python",
70            Self::PyPy | Self::GraalPy => self.long_name(),
71        }
72    }
73
74    /// The name used when installing this implementation as an executable into the bin directory.
75    fn executable_install_name(self) -> &'static str {
76        match self {
77            Self::Pyodide => "pyodide",
78            _ => self.executable_name(),
79        }
80    }
81
82    pub(crate) fn matches_interpreter(self, interpreter: &Interpreter) -> bool {
83        match self {
84            Self::Pyodide => interpreter.os().is_emscripten(),
85            _ => interpreter
86                .implementation_name()
87                .eq_ignore_ascii_case(self.long_name()),
88        }
89    }
90}
91
92impl LenientImplementationName {
93    pub fn pretty(&self) -> &str {
94        match self {
95            Self::Known(implementation) => implementation.pretty(),
96            Self::Unknown(name) => name,
97        }
98    }
99
100    pub(crate) fn executable_install_name(&self) -> &str {
101        match self {
102            Self::Known(implementation) => implementation.executable_install_name(),
103            Self::Unknown(name) => name,
104        }
105    }
106}
107
108impl<'a> From<&'a LenientImplementationName> for &'a str {
109    fn from(value: &'a LenientImplementationName) -> &'a str {
110        match value {
111            LenientImplementationName::Known(implementation) => implementation.long_name(),
112            LenientImplementationName::Unknown(name) => name,
113        }
114    }
115}
116
117impl FromStr for ImplementationName {
118    type Err = Error;
119
120    /// Parse a Python implementation name from a string.
121    ///
122    /// Supports the full name and the platform compatibility tag style name.
123    fn from_str(s: &str) -> Result<Self, Self::Err> {
124        Self::iter_all()
125            .find(|implementation| {
126                s.eq_ignore_ascii_case(implementation.long_name())
127                    || implementation
128                        .short_name()
129                        .is_some_and(|name| s.eq_ignore_ascii_case(name))
130            })
131            .ok_or_else(|| Error::UnknownImplementation(s.to_string()))
132    }
133}
134
135impl Display for ImplementationName {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        f.write_str(self.long_name())
138    }
139}
140
141impl From<&str> for LenientImplementationName {
142    fn from(s: &str) -> Self {
143        match ImplementationName::from_str(s) {
144            Ok(implementation) => Self::Known(implementation),
145            Err(_) => Self::Unknown(s.to_string()),
146        }
147    }
148}
149
150impl From<ImplementationName> for LenientImplementationName {
151    fn from(implementation: ImplementationName) -> Self {
152        Self::Known(implementation)
153    }
154}
155
156impl Display for LenientImplementationName {
157    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158        match self {
159            Self::Known(implementation) => implementation.fmt(f),
160            Self::Unknown(name) => f.write_str(&name.to_ascii_lowercase()),
161        }
162    }
163}