From 055986e2b1910fe7ed65dc1f058eaa2683046113 Mon Sep 17 00:00:00 2001 From: Thomas O'Donnell Date: Sun, 14 Jun 2020 11:27:10 +0200 Subject: [PATCH] feat(python): Add option to change the python binary (#1297) * Add option to change the python binary We are going to start to have problems with the python binary as python2 is removed and replaced with python3. To make the transition easier I have added an option to the python module to allow the user to pick a particular binary, e.g `python3`, for the module to use when selecting the version of python. I have also refactored the python tests moving almost all of them into the module and removing the dependency on the version of python that is installed on the system. * Add advanced config section to python module docs Have added an advanced config section to the python module docs and moved the `python_binary` option into that section. --- docs/config/README.md | 21 +++- src/configs/python.rs | 2 + src/modules/python.rs | 155 +++++++++++++++++++++++++++- src/utils.rs | 8 ++ tests/testsuite/python.rs | 205 +------------------------------------- 5 files changed, 187 insertions(+), 204 deletions(-) diff --git a/docs/config/README.md b/docs/config/README.md index d0380e724..57f6f7e8c 100644 --- a/docs/config/README.md +++ b/docs/config/README.md @@ -1225,6 +1225,25 @@ The module will be shown if any of the following conditions are met: | `style` | `"bold yellow"` | The style for the module. | | `disabled` | `false` | Disables the `python` module. | +
+This module has some advanced configuration options. + +| Variable | Default | Description | +| --------------- | -------- | ---------------------------------------------------------------------------- | +| `python_binary` | `python` | Confgures the python binary that Starship executes when getting the version. | + +The `python_binary` variable changes the binary that Starship executes to get +the version of Python, it doesn't change the arguments that are used. + +```toml +# ~/.config/starship.toml + +[python] +python_binary = "python3" +``` + +
+ ### Example ```toml @@ -1480,7 +1499,7 @@ will simply show all custom modules in the order they were defined. If unset, it will fallback to STARSHIP_SHELL and then to "sh" on Linux, and "cmd /C" on Windows. If `shell` is not given or only contains one element and Starship detects PowerShell will be used, -the following arguments will automatically be added: `-NoProfile -Command -`. +the following arguments will automatically be added: `-NoProfile -Command -`. This behavior can be avoided by explicitly passing arguments to the shell, e.g. ```toml diff --git a/src/configs/python.rs b/src/configs/python.rs index bd986e91c..2b9aa4158 100644 --- a/src/configs/python.rs +++ b/src/configs/python.rs @@ -9,6 +9,7 @@ pub struct PythonConfig<'a> { pub version: SegmentConfig<'a>, pub pyenv_prefix: SegmentConfig<'a>, pub pyenv_version_name: bool, + pub python_binary: &'a str, pub scan_for_pyfiles: bool, pub style: Style, pub disabled: bool, @@ -21,6 +22,7 @@ impl<'a> RootModuleConfig<'a> for PythonConfig<'a> { version: SegmentConfig::default(), pyenv_prefix: SegmentConfig::new("pyenv "), pyenv_version_name: false, + python_binary: "python", scan_for_pyfiles: true, style: Color::Yellow.bold(), disabled: false, diff --git a/src/modules/python.rs b/src/modules/python.rs index 9b6b63b7c..63ca2de6a 100644 --- a/src/modules/python.rs +++ b/src/modules/python.rs @@ -49,7 +49,7 @@ pub fn module<'a>(context: &'a Context) -> Option> { module.create_segment("pyenv_prefix", &config.pyenv_prefix); module.create_segment("version", &SegmentConfig::new(&python_version.trim())); } else { - let python_version = get_python_version()?; + let python_version = get_python_version(&config.python_binary)?; let formatted_version = format_python_version(&python_version); module.create_segment("version", &SegmentConfig::new(&formatted_version)); }; @@ -64,8 +64,8 @@ pub fn module<'a>(context: &'a Context) -> Option> { Some(module) } -fn get_python_version() -> Option { - match utils::exec_cmd("python", &["--version"]) { +fn get_python_version(python_binary: &str) -> Option { + match utils::exec_cmd(python_binary, &["--version"]) { Some(output) => { if output.stdout.is_empty() { Some(output.stderr) @@ -98,6 +98,10 @@ fn get_python_virtual_env() -> Option { #[cfg(test)] mod tests { use super::*; + use crate::modules::utils::test::render_module; + use ansi_term::Color; + use std::fs::File; + use std::io; #[test] fn test_format_python_version() { @@ -110,4 +114,149 @@ mod tests { let input = "Python 3.6.10 :: Anaconda, Inc."; assert_eq!(format_python_version(input), "v3.6.10"); } + + #[test] + fn folder_without_python_files() -> io::Result<()> { + let dir = tempfile::tempdir()?; + let actual = render_module("python", dir.path(), None); + let expected = None; + assert_eq!(expected, actual); + + dir.close() + } + + #[test] + fn folder_with_python_version() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join(".python-version"))?.sync_all()?; + + check_python2_renders(&dir, None); + check_python3_renders(&dir, None); + dir.close() + } + + #[test] + fn folder_with_requirements_txt() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("requirements.txt"))?.sync_all()?; + + check_python2_renders(&dir, None); + check_python3_renders(&dir, None); + dir.close() + } + + #[test] + fn folder_with_pyproject_toml() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("pyproject.toml"))?.sync_all()?; + + check_python2_renders(&dir, None); + check_python3_renders(&dir, None); + dir.close() + } + + #[test] + fn folder_with_pipfile() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("Pipfile"))?.sync_all()?; + + check_python2_renders(&dir, None); + check_python3_renders(&dir, None); + dir.close() + } + + #[test] + fn folder_with_tox() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("tox.ini"))?.sync_all()?; + + check_python2_renders(&dir, None); + check_python3_renders(&dir, None); + dir.close() + } + + #[test] + fn folder_with_setup_py() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("setup.py"))?.sync_all()?; + + check_python2_renders(&dir, None); + check_python3_renders(&dir, None); + dir.close() + } + + #[test] + fn folder_with_init_py() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("__init__.py"))?.sync_all()?; + + check_python2_renders(&dir, None); + check_python3_renders(&dir, None); + dir.close() + } + + #[test] + fn folder_with_py_file() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("main.py"))?.sync_all()?; + + check_python2_renders(&dir, None); + check_python3_renders(&dir, None); + dir.close() + } + + #[test] + fn disabled_scan_for_pyfiles_and_folder_with_ignored_py_file() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("foo.py"))?.sync_all()?; + + let expected = None; + let config = toml::toml! { + [python] + scan_for_pyfiles = false + }; + let actual = render_module("python", dir.path(), Some(config)); + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn disabled_scan_for_pyfiles_and_folder_with_setup_py() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("setup.py"))?.sync_all()?; + + let config = toml::toml! { + [python] + scan_for_pyfiles = false + }; + + check_python2_renders(&dir, Some(config)); + + let config_python3 = toml::toml! { + [python] + python_binary = "python3" + scan_for_pyfiles = false + }; + + check_python3_renders(&dir, Some(config_python3)); + + dir.close() + } + + fn check_python2_renders(dir: &tempfile::TempDir, starship_config: Option) { + let actual = render_module("python", dir.path(), starship_config); + let expected = Some(format!("via {} ", Color::Yellow.bold().paint("🐍 v2.7.17"))); + assert_eq!(expected, actual); + } + + fn check_python3_renders(dir: &tempfile::TempDir, starship_config: Option) { + let config = Some(starship_config.unwrap_or(toml::toml! { + [python] + python_binary = "python3" + })); + + let actual = render_module("python", dir.path(), config); + let expected = Some(format!("via {} ", Color::Yellow.bold().paint("🐍 v3.8.0"))); + assert_eq!(expected, actual); + } } diff --git a/src/utils.rs b/src/utils.rs index 7f7e7f4d4..ee7af608d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -97,6 +97,14 @@ active boot switches: -d:release\n", stdout: String::from("0.13.5"), stderr: String::default(), }), + "python --version" => Some(CommandOutput { + stdout: String::from("Python 2.7.17"), + stderr: String::default(), + }), + "python3 --version" => Some(CommandOutput { + stdout: String::from("Python 3.8.0"), + stderr: String::default(), + }), "ruby -v" => Some(CommandOutput { stdout: String::from("ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux-gnu]"), stderr: String::default(), diff --git a/tests/testsuite/python.rs b/tests/testsuite/python.rs index 9c1d1a8dc..d799f7f90 100644 --- a/tests/testsuite/python.rs +++ b/tests/testsuite/python.rs @@ -1,163 +1,12 @@ use std::fs::File; use std::io; -use ansi_term::Color; +use crate::common; -use crate::common::{self, TestCommand}; +// TODO - These tests should be moved into the python module when we have sorted out mocking of env +// vars. #[test] -fn show_nothing_on_empty_dir() -> io::Result<()> { - let dir = tempfile::tempdir()?; - - let output = common::render_module("python") - .arg("--path") - .arg(dir.path()) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = ""; - assert_eq!(expected, actual); - dir.close() -} - -#[test] -#[ignore] -fn folder_with_python_version() -> io::Result<()> { - let dir = tempfile::tempdir()?; - File::create(dir.path().join(".python-version"))?.sync_all()?; - - let output = common::render_module("python") - .arg("--path") - .arg(dir.path()) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("via {} ", Color::Yellow.bold().paint("🐍 v3.7.7")); - assert_eq!(expected, actual); - dir.close() -} - -#[test] -#[ignore] -fn folder_with_requirements_txt() -> io::Result<()> { - let dir = tempfile::tempdir()?; - File::create(dir.path().join("requirements.txt"))?.sync_all()?; - - let output = common::render_module("python") - .arg("--path") - .arg(dir.path()) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("via {} ", Color::Yellow.bold().paint("🐍 v3.7.7")); - assert_eq!(expected, actual); - dir.close() -} - -#[test] -#[ignore] -fn folder_with_pyproject_toml() -> io::Result<()> { - let dir = tempfile::tempdir()?; - File::create(dir.path().join("pyproject.toml"))?.sync_all()?; - - let output = common::render_module("python") - .arg("--path") - .arg(dir.path()) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("via {} ", Color::Yellow.bold().paint("🐍 v3.7.7")); - assert_eq!(expected, actual); - dir.close() -} - -#[test] -#[ignore] -fn folder_with_pipfile() -> io::Result<()> { - let dir = tempfile::tempdir()?; - File::create(dir.path().join("Pipfile"))?.sync_all()?; - - let output = common::render_module("python") - .arg("--path") - .arg(dir.path()) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("via {} ", Color::Yellow.bold().paint("🐍 v3.7.7")); - assert_eq!(expected, actual); - dir.close() -} - -#[test] -#[ignore] -fn folder_with_tox() -> io::Result<()> { - let dir = tempfile::tempdir()?; - File::create(dir.path().join("tox.ini"))?.sync_all()?; - - let output = common::render_module("python") - .arg("--path") - .arg(dir.path()) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("via {} ", Color::Yellow.bold().paint("🐍 v3.7.7")); - assert_eq!(expected, actual); - dir.close() -} - -#[test] -#[ignore] -fn folder_with_setup_py() -> io::Result<()> { - let dir = tempfile::tempdir()?; - File::create(dir.path().join("setup.py"))?.sync_all()?; - - let output = common::render_module("python") - .arg("--path") - .arg(dir.path()) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("via {} ", Color::Yellow.bold().paint("🐍 v3.7.7")); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -#[ignore] -fn folder_with_init_py() -> io::Result<()> { - let dir = tempfile::tempdir()?; - File::create(dir.path().join("__init__.py"))?.sync_all()?; - - let output = common::render_module("python") - .arg("--path") - .arg(dir.path()) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("via {} ", Color::Yellow.bold().paint("🐍 v3.7.7")); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -#[ignore] -fn folder_with_py_file() -> io::Result<()> { - let dir = tempfile::tempdir()?; - File::create(dir.path().join("main.py"))?.sync_all()?; - - let output = common::render_module("python") - .arg("--path") - .arg(dir.path()) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("via {} ", Color::Yellow.bold().paint("🐍 v3.7.7")); - assert_eq!(expected, actual); - dir.close() -} - -#[test] -#[ignore] fn with_virtual_env() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join("main.py"))?.sync_all()?; @@ -168,13 +17,11 @@ fn with_virtual_env() -> io::Result<()> { .output()?; let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format!("via {} ", Color::Yellow.bold().paint("🐍 v3.7.7 (my_venv)")); - assert_eq!(expected, actual); + assert!(actual.contains("my_venv")); dir.close() } #[test] -#[ignore] fn with_active_venv() -> io::Result<()> { let dir = tempfile::tempdir()?; @@ -185,48 +32,6 @@ fn with_active_venv() -> io::Result<()> { .output()?; let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format!("via {} ", Color::Yellow.bold().paint("🐍 v3.7.7 (my_venv)")); - assert_eq!(expected, actual); + assert!(actual.contains("my_venv")); dir.close() } - -#[test] -fn disabled_scan_for_pyfiles_and_folder_with_ignored_py_file() -> io::Result<()> { - let dir = tempfile::tempdir()?; - File::create(dir.path().join("foo.py"))?.sync_all()?; - - let output = common::render_module("python") - .use_config(toml::toml! { - [python] - scan_for_pyfiles = false - }) - .arg("--path") - .arg(dir.path()) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = ""; - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -#[ignore] -fn disabled_scan_for_pyfiles_and_folder_with_setup_py() -> io::Result<()> { - let dir = tempfile::tempdir()?; - File::create(dir.path().join("setup.py"))?.sync_all()?; - - let output = common::render_module("python") - .use_config(toml::toml! { - [python] - scan_for_pyfiles = false - }) - .arg("--path") - .arg(dir.path()) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("via {} ", Color::Yellow.bold().paint("🐍 v3.7.7")); - assert_eq!(expected, actual); - Ok(()) -}