Skip to content

Commit

Permalink
Document the public API
Browse files Browse the repository at this point in the history
Closes # 46, #47
  • Loading branch information
brettcannon committed Jan 30, 2021
1 parent 9153054 commit 94682b1
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 12 deletions.
23 changes: 16 additions & 7 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Parsing of CLI flags.

use std::{
cmp,
collections::HashMap,
Expand All @@ -13,10 +15,15 @@ use std::{

use crate::{ExactVersion, RequestedVersion};

/// Represents the possible outcomes based on CLI arguments.
#[derive(Debug, PartialEq)]
pub enum Action {
/// The `-h` output for the command itself along with the path to a
/// Python executable to get its own `-h` output.
Help(String, PathBuf),
/// A formatted string listing all found executables.
List(String),
/// Details for executing a found Python executable.
Execute {
launcher_path: PathBuf,
executable: PathBuf,
Expand All @@ -25,6 +32,7 @@ pub enum Action {
}

impl Action {
/// Parses `argv` to determine what action should be taken.
pub fn from_main(argv: &[String]) -> crate::Result<Self> {
let launcher_path = PathBuf::from(&argv[0]); // Strip the path to this executable.

Expand Down Expand Up @@ -121,8 +129,8 @@ fn list_executables(executables: &HashMap<ExactVersion, PathBuf>) -> crate::Resu
// XXX Expose publicly?
/// Returns the path to the activated virtual environment's executable.
///
/// A virtual environment is determined to be activated based on the existence of the `VIRTUAL_ENV`
/// environment variable.
/// A virtual environment is determined to be activated based on the
/// existence of the `VIRTUAL_ENV` environment variable.
fn venv_executable(venv_root: &str) -> PathBuf {
let mut path = PathBuf::new();
path.push(venv_root);
Expand Down Expand Up @@ -187,11 +195,12 @@ fn find_executable(version: RequestedVersion, args: &[String]) -> crate::Result<
chosen_path = Some(venv_executable(&venv_root.to_string_lossy()));
} else if !args.is_empty() {
// Using the first argument because it's the simplest and sanest.
// We can't use the last argument because that could actually be an argument to the
// Python module being executed. This is the same reason we can't go searching for
// the first/last file path that we find. The only safe way to get the file path
// regardless of its position is to replicate Python's arg parsing and that's a
// **lot** of work for little gain. Hence we only care about the first argument.
// We can't use the last argument because that could actually be an argument
// to the Python module being executed. This is the same reason we can't go
// searching for the first/last file path that we find. The only safe way to
// get the file path regardless of its position is to replicate Python's arg
// parsing and that's a **lot** of work for little gain. Hence we only care
// about the first argument.
let possible_file = &args[0];
log::info!("Checking {:?} for a shebang", possible_file);
if let Ok(mut open_file) = File::open(possible_file) {
Expand Down
25 changes: 20 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,24 @@ use std::{
str::FromStr,
};

/// [`std::result::Result`] type with [`Error`] as the error type.
pub type Result<T> = std::result::Result<T, Error>;

/// Error enum for the entire crate.
#[derive(Debug, PartialEq)]
pub enum Error {
// {RequestedVersion, ExactVersion}::from_str
/// String passed to [`RequestedVersion::from_str`] or [`ExactVersion::from_str`]
/// has an expected digit component that cannot be parsed as an integer.
ParseVersionComponentError(ParseIntError),
// RequestedVersion::from_str
/// [`ExactVersion::from_str`] is passed a string missing a `.`.
DotMissing,
// ExactVersion::from_path
/// [`ExactVersion::from_path`] is given a [`Path`] which lacks a file name.
FileNameMissing,
/// [`ExactVersion::from_path`] cannot convert a file name to a string.
FileNameToStrError,
/// An unexpected file name was given to [`ExactVersion::from_path`].
PathFileNameError,
/// No Python executable could be found based on the [`RequestedVersion`].
// cli::{list_executables, find_executable, help}
NoExecutableFound(RequestedVersion),
}
Expand Down Expand Up @@ -61,6 +67,7 @@ impl std::error::Error for Error {

#[cfg(not(tarpaulin_include))]
impl Error {
/// Returns the appropriate [exit code](`exitcode::ExitCode`) for the error.
pub fn exit_code(&self) -> exitcode::ExitCode {
match self {
Self::ParseVersionComponentError(_) => exitcode::USAGE,
Expand All @@ -73,14 +80,17 @@ impl Error {
}
}

/// An integral part of a version specifier (e.g. the `X` or `Y` of `X.Y`).
/// The integral part of a version specifier (e.g. the `X` or `Y` of `X.Y`).
type ComponentSize = u16;

/// Represents the version of Python a user requsted.
/// The version of Python being searched for.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum RequestedVersion {
/// Any version is acceptable.
Any,
/// A specific major version (e.g. `3.x`).
MajorOnly(ComponentSize),
/// The specific `major.minor` version (e.g. `3.9`).
Exact(ComponentSize, ComponentSize),
}

Expand Down Expand Up @@ -124,6 +134,7 @@ impl RequestedVersion {
}
}

/// Specifies the `major.minor` version of a Python executable.
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ExactVersion {
pub major: ComponentSize,
Expand Down Expand Up @@ -170,6 +181,7 @@ fn acceptable_file_name(file_name: &str) -> bool {
}

impl ExactVersion {
/// Constructs a [`ExactVersion`] from a `pythonX.Y` file path.
pub fn from_path(path: &Path) -> Result<Self> {
path.file_name()
.ok_or(Error::FileNameMissing)
Expand All @@ -184,6 +196,7 @@ impl ExactVersion {

// XXX from_shebang()?

/// Tests whether this [`ExactVersion`] satisfies the [`RequestedVersion`].
pub fn supports(&self, requested: RequestedVersion) -> bool {
match requested {
RequestedVersion::Any => true,
Expand Down Expand Up @@ -230,6 +243,7 @@ fn all_executables_in_paths(
executables
}

/// Finds all possible Python executables.
pub fn all_executables() -> HashMap<ExactVersion, PathBuf> {
log::info!("Checking PATH environment variable");
let path_entries = env_path();
Expand All @@ -251,6 +265,7 @@ fn find_executable_in_hashmap(
.map(|pair| pair.1.clone())
}

/// Attempts to find an executable that satisfies a specified [`RequestedVersion`].
pub fn find_executable(requested: RequestedVersion) -> Option<PathBuf> {
let found_executables = all_executables();
find_executable_in_hashmap(requested, &found_executables)
Expand Down

0 comments on commit 94682b1

Please sign in to comment.