Enable `uv run` with a GitHub Gist (#15058)

## Summary

You can now run `uv run
https://gist.github.com/charliermarsh/ea9eab7f56b1b3d41e51960001cae31d`
to execute a single-file Gist without having to go in and copy the raw
URL.
This commit is contained in:
Charlie Marsh 2025-08-05 00:38:20 +01:00 committed by GitHub
parent a28c3fb7d9
commit 3b15da3c5d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 77 additions and 1 deletions

View File

@ -42,6 +42,17 @@ use uv_warnings::warn_user;
use uv_workspace::{DiscoveryOptions, VirtualProject, Workspace, WorkspaceCache, WorkspaceError};
use crate::child::run_to_completion;
/// GitHub Gist API response structure
#[derive(serde::Deserialize)]
struct GistResponse {
files: std::collections::HashMap<String, GistFile>,
}
#[derive(serde::Deserialize)]
struct GistFile {
raw_url: String,
}
use crate::commands::pip::loggers::{
DefaultInstallLogger, DefaultResolveLogger, SummaryInstallLogger, SummaryResolveLogger,
};
@ -1596,6 +1607,66 @@ impl std::fmt::Display for RunCommand {
}
}
/// Resolve a GitHub Gist URL to its raw file URL using the GitHub API.
async fn resolve_gist_url(
url: &DisplaySafeUrl,
network_settings: &NetworkSettings,
) -> anyhow::Result<DisplaySafeUrl> {
// Extract the Gist ID from the URL.
let gist_id = url
.path_segments()
.and_then(|mut segments| segments.nth(1))
.ok_or_else(|| anyhow!("Invalid Gist URL format"))?;
// Build the API URL.
let api_url = format!("https://api.github.com/gists/{gist_id}");
let client = BaseClientBuilder::new()
.retries_from_env()?
.connectivity(network_settings.connectivity)
.native_tls(network_settings.native_tls)
.allow_insecure_host(network_settings.allow_insecure_host.clone())
.build();
// Build the request with appropriate headers.
let api_url_parsed = DisplaySafeUrl::parse(&api_url)?;
let mut request = client
.for_host(&api_url_parsed)
.get(Url::from(api_url_parsed));
request = request.header("Accept", "application/vnd.github.v3+json");
// Add GitHub token, if available.
if let Ok(token) = std::env::var(EnvVars::UV_GITHUB_TOKEN) {
request = request.header("Authorization", format!("Bearer {token}"));
}
// Make the API request.
let response = request.send().await?;
response.error_for_status_ref()?;
// Parse the response
let gist_data: GistResponse = response.json().await?;
// Get the raw URL of the first `.py` file (or just the first file).
let raw_url = gist_data
.files
.iter()
.filter(|(name, _)| {
Path::new(name)
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("py"))
})
.map(|(_, file)| &file.raw_url)
.next()
// If no `.py` file is found, use the first file.
.or_else(|| gist_data.files.values().next().map(|file| &file.raw_url))
.ok_or_else(|| anyhow!("No files found in the Gist"))?;
let url = DisplaySafeUrl::parse(raw_url)?;
Ok(url)
}
impl RunCommand {
/// Determine the [`RunCommand`] for a given set of arguments.
#[allow(clippy::fn_params_excessive_bools)]
@ -1633,7 +1704,12 @@ impl RunCommand {
// We don't do this check on Windows since the file path would
// be invalid anyway, and thus couldn't refer to a local file.
if !cfg!(unix) || matches!(target_path.try_exists(), Ok(false)) {
let url = DisplaySafeUrl::parse(&target.to_string_lossy())?;
let mut url = DisplaySafeUrl::parse(&target.to_string_lossy())?;
// If it's a Gist URL, use the GitHub API to get the raw URL.
if url.host_str() == Some("gist.github.com") {
url = resolve_gist_url(&url, &network_settings).await?;
}
let file_stem = url
.path_segments()