cli: conventionally treat - as "read from stdin" (#1314)

Basically, when a path to a requirements file is `-`, then we should
read its contents from `stdin` instead of the file path named `-`.

Fixes #1313
This commit is contained in:
Andrew Gallant 2024-02-15 11:25:56 -05:00 committed by GitHub
parent c5e413c4b8
commit b6fba00153
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 69 additions and 6 deletions

View file

@ -12,6 +12,38 @@ pub use crate::path::*;
mod path;
/// Reads the contents of the file path given into memory.
///
/// If the file path is `-`, then contents are read from stdin instead.
pub fn read(path: impl AsRef<Path>) -> std::io::Result<Vec<u8>> {
use std::io::Read;
let path = path.as_ref();
if path == Path::new("-") {
let mut buf = Vec::with_capacity(1024);
std::io::stdin().read_to_end(&mut buf)?;
Ok(buf)
} else {
fs::read(path)
}
}
/// Reads the contents of the file path given into memory as a `String`.
///
/// If the file path is `-`, then contents are read from stdin instead.
pub fn read_to_string(path: impl AsRef<Path>) -> std::io::Result<String> {
use std::io::Read;
let path = path.as_ref();
if path == Path::new("-") {
let mut buf = String::with_capacity(1024);
std::io::stdin().read_to_string(&mut buf)?;
Ok(buf)
} else {
fs::read_to_string(path)
}
}
/// Create a symlink from `src` to `dst`, replacing any existing symlink.
///
/// On Windows, this uses the `junction` crate to create a junction point.

View file

@ -158,6 +158,8 @@ fn date_or_datetime(input: &str) -> Result<DateTime<Utc>, String> {
#[allow(clippy::struct_excessive_bools)]
struct PipCompileArgs {
/// Include all packages listed in the given `requirements.in` files.
///
/// When the path is `-`, then requirements are read from stdin.
#[clap(required(true))]
src_file: Vec<PathBuf>,

View file

@ -4,7 +4,6 @@ use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use console::Term;
use fs_err as fs;
use rustc_hash::FxHashSet;
use distribution_types::{FlatIndexLocation, IndexUrl};
@ -193,7 +192,7 @@ impl RequirementsSpecification {
}
}
RequirementsSource::PyprojectToml(path) => {
let contents = fs::read_to_string(path)?;
let contents = puffin_fs::read_to_string(path)?;
let pyproject_toml = toml::from_str::<pyproject_toml::PyProjectToml>(&contents)
.with_context(|| format!("Failed to parse `{}`", path.normalized_display()))?;
let mut used_extras = FxHashSet::default();

View file

@ -47,6 +47,36 @@ fn compile_requirements_in() -> Result<()> {
Ok(())
}
/// Resolve a specific version of Django from a `requirements.in` file on stdin
/// when passed a path of `-`.
#[test]
fn compile_requirements_in_stdin() -> Result<()> {
let context = TestContext::new("3.12");
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("django==5.0b1")?;
puffin_snapshot!(context
.compile()
.stdin(fs::File::open(requirements_in)?)
.arg("-"), @r###"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by Puffin v[VERSION] via the following command:
# puffin pip compile --cache-dir [CACHE_DIR] --exclude-newer 2023-11-18T12:00:00Z -
asgiref==3.7.2
# via django
django==5.0b1
sqlparse==0.4.4
# via django
----- stderr -----
Resolved 3 packages in [TIME]
"###);
Ok(())
}
#[test]
fn missing_requirements_in() {
let context = TestContext::new("3.12");

View file

@ -39,7 +39,6 @@ use std::fmt::{Display, Formatter};
use std::io;
use std::path::{Path, PathBuf};
use fs_err as fs;
use serde::{Deserialize, Serialize};
use tracing::warn;
use unscanny::{Pattern, Scanner};
@ -299,11 +298,12 @@ impl RequirementsTxt {
requirements_txt: impl AsRef<Path>,
working_dir: impl AsRef<Path>,
) -> Result<Self, RequirementsTxtFileError> {
let content =
fs::read_to_string(&requirements_txt).map_err(|err| RequirementsTxtFileError {
let content = puffin_fs::read_to_string(&requirements_txt).map_err(|err| {
RequirementsTxtFileError {
file: requirements_txt.as_ref().to_path_buf(),
error: RequirementsTxtParserError::IO(err),
})?;
}
})?;
let data = Self::parse_inner(&content, working_dir.as_ref()).map_err(|err| {
RequirementsTxtFileError {
file: requirements_txt.as_ref().to_path_buf(),