pyenv_python/
version.rs

1use std::{env, io};
2use std::fs::File;
3use std::io::{BufRead, BufReader, ErrorKind};
4use std::path::{Path, PathBuf};
5
6use crate::{PyenvVersion, PyenvVersionFrom};
7
8trait FlipResult<T, E> {
9    fn flip(self) -> Result<E, T>;
10}
11
12impl<T, E> FlipResult<T, E> for Result<T, E> {
13    fn flip(self) -> Result<E, T> {
14        match self {
15            Ok(t) => Err(t),
16            Err(e) => Ok(e),
17        }
18    }
19}
20
21impl PyenvVersion {
22    pub fn from(from: PyenvVersionFrom) -> impl Fn(String) -> Self {
23        move |version| Self { version, from }
24    }
25}
26
27fn read_python_version_file(path: &Path) -> io::Result<String> {
28    let file = File::open(path)?;
29    let reader = BufReader::new(file);
30    let version = reader.lines().next().ok_or(ErrorKind::NotFound)??;
31    Ok(version)
32}
33
34fn from_local_python_version_file_given_cwd(cwd: &Path) -> Result<io::Error, String> {
35    for dir in cwd.ancestors() {
36        let path = dir.join(".python-version");
37        read_python_version_file(path.as_path()).flip()?;
38    }
39    Ok(ErrorKind::NotFound.into())
40}
41
42fn from_local_python_version_file() -> io::Result<String> {
43    let cwd = env::current_dir()?;
44    let version = from_local_python_version_file_given_cwd(cwd.as_path()).flip()?;
45    Ok(version)
46}
47
48fn global_python_version_file_path(root: &Path) -> PathBuf {
49    root.join("version")
50}
51
52fn from_global_python_version_file(root: &Path) -> io::Result<String> {
53    let path = global_python_version_file_path(root);
54    read_python_version_file(path.as_path())
55}
56
57// use inverted Result<>s here to short circuit on success instead of failure
58fn as_result(root: &Path) -> Result<(), PyenvVersion> {
59    use PyenvVersionFrom::*;
60    env::var("PYENV_VERSION").map(PyenvVersion::from(Shell)).flip()?;
61    from_local_python_version_file().map(PyenvVersion::from(Local)).flip()?;
62    from_global_python_version_file(root).map(PyenvVersion::from(Global)).flip()?;
63    Ok(())
64}
65
66pub fn pyenv_version(root: &Path) -> Option<PyenvVersion> {
67    as_result(root).err()
68}