pyflow 0.0.4

A modern Python dependency manager
use crate::util;
use regex::Regex;
//use std::process::Stdio;
use std::{error::Error, fmt};
use std::{path::Path, process::Command};

#[derive(Debug)]
struct _ExecutionError {
    details: String,
}

impl Error for _ExecutionError {
    fn description(&self) -> &str {
        &self.details
    }
}

impl fmt::Display for _ExecutionError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.details)
    }
}

/// Find the py_version from the `python --py_version` command. Eg: "Python 3.7".
pub fn find_py_version(alias: &str) -> Option<crate::Version> {
    let output = Command::new(alias).arg("--version").output();

    let output_bytes = match output {
        Ok(ob) => {
            // Old versions of python output `--version` to `stderr`; newer ones to `stdout`,
            // so check both.
            if ob.stdout.is_empty() {
                ob.stderr
            } else {
                ob.stdout
            }
        }
        Err(_) => return None,
    };

    if let Ok(version) = std::str::from_utf8(&output_bytes) {
        let re = Regex::new(r"Python\s+(\d{1,4})\.(\d{1,4})\.(\d{1,4})").unwrap();
        match re.captures(version) {
            Some(caps) => {
                let major = caps.get(1).unwrap().as_str().parse::<u32>().unwrap();
                let minor = caps.get(2).unwrap().as_str().parse::<u32>().unwrap();
                let patch = caps.get(3).unwrap().as_str().parse::<u32>().unwrap();
                Some(crate::Version::new(major, minor, patch))
            }
            None => None,
        }
    } else {
        None
    }
}

/// Create the virtual env. Assume we're running Python 3.3+, where `venv` is included.
/// Additionally, create the __pypackages__ directory if not already created.
pub(crate) fn create_venv(
    py_alias: &str,
    lib_path: &Path,
    name: &str,
) -> Result<(), Box<dyn Error>> {
    // While creating the lib path, we're creating the __pypackages__ structure.
    Command::new(py_alias)
        .args(&["-m", "venv", name])
        .current_dir(lib_path.join("../"))
        .output()?;

    Ok(())
}

// todo: DRY for using a path instead of str. use impl Into<PathBuf> ?
pub(crate) fn create_venv2(
    py_alias: &Path,
    lib_path: &Path,
    name: &str,
) -> Result<(), Box<dyn Error>> {
    // While creating the lib path, we're creating the __pypackages__ structure.
    Command::new(py_alias)
        .args(&["-m", "venv", name])
        .current_dir(lib_path.join("../"))
        .output()?;

    Ok(())
}

pub(crate) fn run_python(
    bin_path: &Path,
    lib_path: &Path,
    args: &[String],
) -> Result<(), Box<dyn Error>> {
    util::set_pythonpath(lib_path);

    // Run this way instead of setting current_dir, so we can load files from the right place.
    // Running with .output() prevents the REPL from running, and .spawn() causes
    // permission errors when importing modules.
    //    let mut child = Command::new(bin_path.join("python"))
    //        .args(args)
    //        .stdin(Stdio::piped())
    //        .stdout(Stdio::piped())
    //        .spawn()?;
    //
    //    let stdin = child.stdin.as_mut().expect("failed to get stdin");
    //    stdin.write_all(b"test").expect("failed to write to stdin");
    //
    //    let output = child.wait_with_output().expect("failed to wait on child");

    //    let output = a.wait_with_output().expect("Failed to wait on sed");
    //    println!("{:?}", output.stdout.as_slice());
    //    Command::new(bin_path.join("python")).args(args).status()?;

    Command::new(bin_path.join("python")).args(args).status()?;

    //    Command::new(bin_path.join("python")).args(args).spawn()?;
    //    let output = Command::new(bin_path.join("python")).args(args).output()?;
    //    println!("Output: {:?}", &output);

    Ok(())
}