diff --git a/crates/puffin-fs/src/lib.rs b/crates/puffin-fs/src/lib.rs index 7961c8df5..b831889c1 100644 --- a/crates/puffin-fs/src/lib.rs +++ b/crates/puffin-fs/src/lib.rs @@ -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) -> std::io::Result> { + 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) -> std::io::Result { + 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. diff --git a/crates/puffin/src/main.rs b/crates/puffin/src/main.rs index 74826cdca..42858d2ad 100644 --- a/crates/puffin/src/main.rs +++ b/crates/puffin/src/main.rs @@ -158,6 +158,8 @@ fn date_or_datetime(input: &str) -> Result, 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, diff --git a/crates/puffin/src/requirements.rs b/crates/puffin/src/requirements.rs index 72d2cd9aa..315d0abf3 100644 --- a/crates/puffin/src/requirements.rs +++ b/crates/puffin/src/requirements.rs @@ -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::(&contents) .with_context(|| format!("Failed to parse `{}`", path.normalized_display()))?; let mut used_extras = FxHashSet::default(); diff --git a/crates/puffin/tests/pip_compile.rs b/crates/puffin/tests/pip_compile.rs index fc0cca862..dee0559cb 100644 --- a/crates/puffin/tests/pip_compile.rs +++ b/crates/puffin/tests/pip_compile.rs @@ -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"); diff --git a/crates/requirements-txt/src/lib.rs b/crates/requirements-txt/src/lib.rs index d73396bf4..5bb5a3458 100644 --- a/crates/requirements-txt/src/lib.rs +++ b/crates/requirements-txt/src/lib.rs @@ -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, working_dir: impl AsRef, ) -> Result { - 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(),