Skip to content
This repository was archived by the owner on Jan 21, 2026. It is now read-only.

Commit 69ba8e1

Browse files
committed
add support for streaming response to stdout
1 parent bd329c1 commit 69ba8e1

File tree

6 files changed

+81
-21
lines changed

6 files changed

+81
-21
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ soar-dl --github "pkgforge/soar" --gitlab "18817634" --output "final/"
4343
# Don't do this. The last download will replace the existing file
4444
# Only use file in output path if you're downloading single file.
4545
soar-dl --github "pkgforge/soar" --gitlab "18817634" --output "final"
46+
47+
# Extract archives automatically (only `tar.gz`, `tar.xz`, `tar.zstd`, `tar.bz2`, and `zip` are supported)
48+
soar-dl "https://github.com/pkgforge/soar/releases/download/v0.5.14/soar-x86_64-linux.tar.gz" --extract
49+
50+
# Stream response to stdout
51+
# If you like to pipe the response to other commands, also use quiet mode `-q` to silence other outputs
52+
soar-dl "https://github.com/pkgforge/soar/releases/download/v0.5.14/soar-x86_64-linux.tar.gz" -O-
4653
```
4754

4855
## Command Line Options
@@ -65,6 +72,8 @@ Options:
6572
-c, --concurrency <CONCURRENCY> GHCR concurrency
6673
--ghcr-api <GHCR_API> GHCR API to use
6774
--exact-case Whether to use exact case matching for keywords
75+
--extract Extract supported archive automatically
76+
-q, --quiet Quiet mode
6877
-h, --help Print help
6978
-V, --version Print version
7079
```

src/bin/soar-dl/cli.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,8 @@ pub struct Args {
6464
/// Extract supported archive automatically
6565
#[arg(required = false, long)]
6666
pub extract: bool,
67+
68+
/// Quiet mode
69+
#[arg(required = false, long, short)]
70+
pub quiet: bool,
6771
}

src/bin/soar-dl/download_manager.rs

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use soar_dl::{
1515
},
1616
};
1717

18-
use crate::cli::Args;
18+
use crate::{cli::Args, error, info};
1919

2020
pub struct DownloadManager {
2121
args: Args,
@@ -86,7 +86,7 @@ impl DownloadManager {
8686

8787
let selected_asset = self.select_asset(&assets)?;
8888

89-
println!("Downloading asset from {}", selected_asset.download_url());
89+
info!("Downloading asset from {}", selected_asset.download_url());
9090
handler.download(&selected_asset, options.clone()).await?;
9191
Ok(())
9292
}
@@ -98,12 +98,12 @@ impl DownloadManager {
9898

9999
let handler = ReleaseHandler::<Github>::new();
100100
for project in &self.args.github {
101-
println!("Fetching releases from GitHub: {}", project);
101+
info!("Fetching releases from GitHub: {}", project);
102102
if let Err(e) = self
103103
.handle_platform_download::<Github, GithubRelease, GithubAsset>(&handler, project)
104104
.await
105105
{
106-
eprintln!("{}", e);
106+
error!("{}", e);
107107
}
108108
}
109109
Ok(())
@@ -116,12 +116,12 @@ impl DownloadManager {
116116

117117
let handler = ReleaseHandler::<Gitlab>::new();
118118
for project in &self.args.gitlab {
119-
println!("Fetching releases from GitLab: {}", project);
119+
info!("Fetching releases from GitLab: {}", project);
120120
if let Err(e) = self
121121
.handle_platform_download::<Gitlab, GitlabRelease, GitlabAsset>(&handler, project)
122122
.await
123123
{
124-
eprintln!("{}", e);
124+
error!("{}", e);
125125
}
126126
}
127127
Ok(())
@@ -144,7 +144,7 @@ impl DownloadManager {
144144
let mut retries = 0;
145145
loop {
146146
if retries > 5 {
147-
eprintln!("Max retries exhausted. Aborting.");
147+
error!("Max retries exhausted. Aborting.");
148148
break;
149149
}
150150
match downloader.download_oci().await {
@@ -157,7 +157,7 @@ impl DownloadManager {
157157
| DownloadError::ChunkError,
158158
) => thread::sleep(Duration::from_secs(5)),
159159
Err(err) => {
160-
eprintln!("{}", err);
160+
error!("{}", err);
161161
break;
162162
}
163163
};
@@ -173,7 +173,7 @@ impl DownloadManager {
173173
}
174174

175175
for reference in &self.args.ghcr {
176-
println!("Downloading using OCI reference: {}", reference);
176+
info!("Downloading using OCI reference: {}", reference);
177177

178178
self.handle_oci_download(reference).await?;
179179
}
@@ -185,7 +185,7 @@ impl DownloadManager {
185185
for link in &self.args.links {
186186
match PlatformUrl::parse(link) {
187187
Ok(PlatformUrl::DirectUrl(url)) => {
188-
println!("Downloading using direct link: {}", url);
188+
info!("Downloading using direct link: {}", url);
189189

190190
let options = DownloadOptions {
191191
url: link.clone(),
@@ -196,39 +196,39 @@ impl DownloadManager {
196196
let _ = downloader
197197
.download(options)
198198
.await
199-
.map_err(|e| eprintln!("{}", e));
199+
.map_err(|e| error!("{}", e));
200200
}
201201
Ok(PlatformUrl::Github(project)) => {
202-
println!("Detected GitHub URL, processing as GitHub release");
202+
info!("Detected GitHub URL, processing as GitHub release");
203203
let handler = ReleaseHandler::<Github>::new();
204204
if let Err(e) = self
205205
.handle_platform_download::<Github, GithubRelease, GithubAsset>(
206206
&handler, &project,
207207
)
208208
.await
209209
{
210-
eprintln!("{}", e);
210+
error!("{}", e);
211211
}
212212
}
213213
Ok(PlatformUrl::Gitlab(project)) => {
214-
println!("Detected GitLab URL, processing as GitLab release");
214+
info!("Detected GitLab URL, processing as GitLab release");
215215
let handler = ReleaseHandler::<Gitlab>::new();
216216
if let Err(e) = self
217217
.handle_platform_download::<Gitlab, GitlabRelease, GitlabAsset>(
218218
&handler, &project,
219219
)
220220
.await
221221
{
222-
eprintln!("{}", e);
222+
error!("{}", e);
223223
}
224224
}
225225
Ok(PlatformUrl::Oci(url)) => {
226-
println!("Downloading using OCI reference: {}", url);
226+
info!("Downloading using OCI reference: {}", url);
227227
if let Err(e) = self.handle_oci_download(&url).await {
228-
eprintln!("{}", e);
228+
error!("{}", e);
229229
};
230230
}
231-
Err(err) => eprintln!("Error parsing URL '{}' : {}", link, err),
231+
Err(err) => error!("Error parsing URL '{}' : {}", link, err),
232232
};
233233
}
234234
Ok(())
@@ -243,13 +243,13 @@ impl DownloadManager {
243243
return Ok(assets[0].clone());
244244
}
245245

246-
println!("\nAvailable assets:");
246+
info!("\nAvailable assets:");
247247
for (i, asset) in assets.iter().enumerate() {
248248
let size = asset
249249
.size()
250250
.map(|s| format!(" ({})", HumanBytes(s)))
251251
.unwrap_or_default();
252-
println!("{}. {}{}", i + 1, asset.name(), size);
252+
info!("{}. {}{}", i + 1, asset.name(), size);
253253
}
254254

255255
loop {
@@ -261,7 +261,7 @@ impl DownloadManager {
261261

262262
match input.trim().parse::<usize>() {
263263
Ok(n) if n > 0 && n <= assets.len() => return Ok(assets[n - 1].clone()),
264-
_ => println!("Invalid selection, please try again."),
264+
_ => error!("Invalid selection, please try again."),
265265
}
266266
}
267267
}

src/bin/soar-dl/log.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use std::sync::atomic::{AtomicBool, Ordering};
2+
3+
static QUIET_MODE: AtomicBool = AtomicBool::new(false);
4+
5+
pub fn init(quiet: bool) {
6+
QUIET_MODE.store(quiet, Ordering::SeqCst);
7+
}
8+
9+
pub fn is_quiet() -> bool {
10+
QUIET_MODE.load(Ordering::SeqCst)
11+
}
12+
13+
#[macro_export]
14+
macro_rules! info {
15+
($($arg:tt)*) => {
16+
if !$crate::log::is_quiet() {
17+
println!("{}", format!($($arg)*));
18+
}
19+
};
20+
}
21+
22+
#[macro_export]
23+
macro_rules! error {
24+
($($arg:tt)*) => {
25+
if !$crate::log::is_quiet() {
26+
eprintln!("{}", format!($($arg)*));
27+
}
28+
};
29+
}

src/bin/soar-dl/main.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ use progress::create_progress_bar;
77

88
mod cli;
99
mod download_manager;
10+
mod log;
1011
mod progress;
1112

1213
#[tokio::main]
1314
async fn main() {
1415
let args = Args::parse();
16+
17+
log::init(args.quiet);
18+
1519
let progress_bar = create_progress_bar();
1620
let progress_callback = Arc::new(move |state| progress::handle_progress(state, &progress_bar));
1721

src/downloader.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::{
22
collections::{HashMap, HashSet},
33
fs::Permissions,
4+
io::Write,
45
os::unix::fs::PermissionsExt,
56
path::{Path, PathBuf},
67
sync::{Arc, Mutex},
@@ -83,6 +84,19 @@ impl Downloader {
8384

8485
let content_length = response.content_length().unwrap_or(0);
8586

87+
if options.output_path.as_deref() == Some("-") {
88+
let stdout = std::io::stdout();
89+
let mut stdout_lock = stdout.lock();
90+
let mut stream = response.bytes_stream();
91+
92+
while let Some(chunk) = stream.next().await {
93+
let chunk = chunk.unwrap();
94+
stdout_lock.write_all(&chunk).unwrap();
95+
stdout_lock.flush().unwrap();
96+
}
97+
return Ok("-".to_string());
98+
}
99+
86100
let filename = options
87101
.output_path
88102
.or_else(|| {

0 commit comments

Comments
 (0)