Skip to content

Commit

Permalink
Error out if other flags are specified with --list or --help
Browse files Browse the repository at this point in the history
Fixes #80
  • Loading branch information
brettcannon committed Feb 27, 2021
1 parent 1f97216 commit 66db6a5
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 29 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "python-launcher"
description = "The Python launcher for Unix"
version = "0.14.3"
version = "0.15.0"
authors = ["Brett Cannon <brett@python.org>"]
repository = "https://github.com/brettcannon/python-launcher"
readme = "README.md"
Expand Down
29 changes: 19 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,25 @@
An implementation of the `py` command for Unix-based platforms
(with some potential experimentation for good measure 😉)

The goal is to have `py` become the cross-platform command that all Python users
use when executing a Python interpreter. By having a version-agnostic command
it side-steps the "what should the `python` command point to?" debate by
clearly specifying that upfront (i.e. the newest version of Python that can be
found). This also unifies the suggested command to document for launching
Python on both Windows as Unix as `py` which has existed as the preferred
[command on Windows](https://docs.python.org/3/using/windows.html#launcher) for
some time.

See the top section of `py --help` for instructions.
The goal is to have `py` become the cross-platform command that Python users
typically use to launch an interpreter. By having a command that is
version-agnostic command when it comes to Python, it side-steps the "what should
the `python` command point to?" debate by clearly specifying that upfront (i.e.
the newest version of Python that can be found). This also unifies the suggested
command to document for launching Python on both Windows as Unix as `py` has
existed as the preferred
[command on Windows](https://docs.python.org/3/using/windows.html#launcher)
since 2012 with the release of Python 3.3.

A non-goal of this project is to become the way to launch the Python
interpreter _all the time_. If you know the exact interpreter you want to launch
then you should launch it directly; same goes for when you have
requirements on the type of interpreter you want (e.g. 32-bit, framework build
on macOS, etc.). The Python Launcher should be viewed as a tool of convenience,
not necessity.

For instructions on how to use the Python Launcher, see the top section of
`py --help`.

## Installation

Expand Down
5 changes: 3 additions & 2 deletions src/HELP.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ Python Launcher for Unix {}
usage: {} [launcher-args] [python-args]

Launcher arguments:
-h/--help: This output
--list : List all known interpreters (except activated virtual environment).
-h/--help: This output; must be specified on its own.
--list : List all known interpreters (except activated virtual environment);
must be specified on its own.
-[X] : Launch the latest Python `X` version (e.g. `-3` for the latest
Python 3); PY_PYTHON[X] overrides what is considered the latest
(e.g. `PY_PYTHON3=3.6` will cause `-3` to search for Python 3.6).
Expand Down
39 changes: 24 additions & 15 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,24 @@ impl Action {
let launcher_path = PathBuf::from(&argv[0]); // Strip the path to this executable.

match argv.get(1) {
Some(help) if help == "-h" || help == "--help" => {
crate::find_executable(RequestedVersion::Any)
.ok_or(crate::Error::NoExecutableFound(RequestedVersion::Any))
.map(|executable_path| {
Action::Help(
help_message(&launcher_path, &executable_path),
executable_path,
)
})
}
Some(list) if list == "--list" => {
Ok(Action::List(list_executables(&crate::all_executables())?))
Some(flag) if flag == "-h" || flag == "--help" || flag == "--list" => {
if argv.len() > 2 {
Err(crate::Error::IllegalArgument(
launcher_path,
flag.to_string(),
))
} else if flag == "--list" {
Ok(Action::List(list_executables(&crate::all_executables())?))
} else {
crate::find_executable(RequestedVersion::Any)
.ok_or(crate::Error::NoExecutableFound(RequestedVersion::Any))
.map(|executable_path| {
Action::Help(
help_message(&launcher_path, &executable_path),
executable_path,
)
})
}
}
// TODO: Figure out how to store the result of the version_from_flag() call.
Some(version) if version_from_flag(version).is_some() => {
Expand Down Expand Up @@ -118,7 +124,6 @@ fn list_executables(executables: &HashMap<ExactVersion, PathBuf>) -> crate::Resu
Ok(table.to_string() + "\n")
}

// XXX Expose publicly?
/// Returns the path to the activated virtual environment's executable.
///
/// A virtual environment is determined to be activated based on the
Expand Down Expand Up @@ -156,7 +161,6 @@ fn venv_executable() -> Option<PathBuf> {
activated_venv().or_else(venv_in_dir)
}

// XXX Expose publicly?
// https://en.m.wikipedia.org/wiki/Shebang_(Unix)
fn parse_python_shebang(reader: &mut impl Read) -> Option<RequestedVersion> {
let mut shebang_buffer = [0; 2];
Expand Down Expand Up @@ -199,7 +203,6 @@ fn parse_python_shebang(reader: &mut impl Read) -> Option<RequestedVersion> {
None
}

// XXX Expose publicly?
fn find_executable(version: RequestedVersion, args: &[String]) -> crate::Result<PathBuf> {
let mut requested_version = version;
let mut chosen_path: Option<PathBuf> = None;
Expand Down Expand Up @@ -252,6 +255,12 @@ mod tests {

use super::*;

#[test_case(&["py".to_string(), "--help".to_string(), "--list".to_string()] => Err(crate::Error::IllegalArgument(PathBuf::from("py"), "--help".to_string())))]
#[test_case(&["py".to_string(), "--list".to_string(), "--help".to_string()] => Err(crate::Error::IllegalArgument(PathBuf::from("py"), "--list".to_string())))]
fn from_main_illegal_argument_tests(argv: &[String]) -> crate::Result<Action> {
Action::from_main(argv)
}

#[test_case("-S" => None ; "unrecognized short flag is None")]
#[test_case("--something" => None ; "unrecognized long flag is None")]
#[test_case("-3" => Some(RequestedVersion::MajorOnly(3)) ; "major version")]
Expand Down
14 changes: 14 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ pub enum Error {
/// No Python executable could be found based on the [`RequestedVersion`].
// cli::{list_executables, find_executable, help}
NoExecutableFound(RequestedVersion),
/// Multiple CLI flags given when the first flag that is expected to be specified
/// on its own.
// cli::Action::from_main
IllegalArgument(PathBuf, String),
}

#[cfg(not(tarpaulin_include))]
Expand All @@ -47,6 +51,14 @@ impl fmt::Display for Error {
"No executable found for {}",
requested_version.to_string()
),
Self::IllegalArgument(launcher_path, flag) => {
write!(
f,
"The `{}` flag must be specified on its own; see `{} --help` for details",
flag,
launcher_path.to_string_lossy()
)
}
}
}
}
Expand All @@ -61,6 +73,7 @@ impl std::error::Error for Error {
Self::FileNameToStrError => None,
Self::PathFileNameError => None,
Self::NoExecutableFound(_) => None,
Self::IllegalArgument(_, _) => None,
}
}
}
Expand All @@ -76,6 +89,7 @@ impl Error {
Self::FileNameToStrError => exitcode::SOFTWARE,
Self::PathFileNameError => exitcode::SOFTWARE,
Self::NoExecutableFound(_) => exitcode::USAGE,
Self::IllegalArgument(_, _) => exitcode::USAGE,
}
}
}
Expand Down

0 comments on commit 66db6a5

Please sign in to comment.