Skip to content

Commit

Permalink
feat(cli): add --ordering option for shorten
Browse files Browse the repository at this point in the history
This commit adds new option `--ordering=<ordered|unordered>` for the
`shorten` command.

The ordering variants specify whether the output follows the order of
URLs given as arguments (`ordered`) or not (`unordered`). Preserving the
input order is the default, however, the allowing arbitrary order may be
more efficient (especially with larder values of `--max-concurrent`).

Another difference is in the output format. Since it would be difficult
to match the shortened links of `unordered` back to the inputs, with
this option the output additionally includes the long URLs at each line.

 - Ordered output line: `<short-url>\n`
 - Unordered output line: `<short-url>\t<long-url>\n`
  • Loading branch information
matyama committed Jul 18, 2024
1 parent 314111a commit 26dc0a4
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 32 deletions.
42 changes: 25 additions & 17 deletions src/api.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use std::borrow::Cow;
use std::future::Future;
use std::sync::{Arc, OnceLock};

use futures_util::stream::{self, Stream, StreamExt as _};
use futures_util::stream::{self, BoxStream, Stream, StreamExt as _};
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use url::Url;

use crate::cache::BitlinkCache;
use crate::cli::Ordering;
use crate::config::Config;
use crate::error::{Error, Result};

Expand Down Expand Up @@ -58,8 +60,8 @@ impl std::fmt::Debug for Shorten<'_> {
#[derive(Debug, Deserialize, PartialEq, Eq)]
pub struct Bitlink {
pub link: Url,
#[allow(dead_code)]
pub id: String,
pub long_url: Url,
}

impl std::fmt::Display for Bitlink {
Expand Down Expand Up @@ -180,18 +182,14 @@ impl ClientInner {
result
}

async fn shorten_all(
fn shorten_all(
self: Arc<Self>,
urls: impl IntoIterator<Item = Url>,
) -> impl Stream<Item = Result<Bitlink>> {
let max_concurrent = self.cfg.max_concurrent;

stream::iter(urls)
.map(move |url| {
let client = Arc::clone(&self);
async move { client.shorten(url).await }
})
.buffered(max_concurrent)
) -> impl Stream<Item = impl Future<Output = Result<Bitlink>>> {
stream::iter(urls).map(move |url| {
let client = Arc::clone(&self);
async move { client.shorten(url).await }
})
}
}

Expand All @@ -215,11 +213,21 @@ impl Client {
}
}

pub async fn shorten(
&self,
urls: impl IntoIterator<Item = Url>,
) -> impl Stream<Item = Result<Bitlink>> {
pub fn shorten<'a, I>(&self, urls: I, ordering: Ordering) -> BoxStream<'a, Result<Bitlink>>
where
I: IntoIterator<Item = Url> + 'a,
<I as IntoIterator>::IntoIter: Send,
{
let client = Arc::clone(&self.inner);
client.shorten_all(urls).await
let max_concurrent = client.cfg.max_concurrent;

match ordering {
Ordering::Ordered => client.shorten_all(urls).buffered(max_concurrent).boxed(),

Ordering::Unordered => client
.shorten_all(urls)
.buffer_unordered(max_concurrent)
.boxed(),
}
}
}
4 changes: 3 additions & 1 deletion src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ impl BitlinkCache {
pub async fn get(&self, query: &Shorten<'_>) -> Option<Bitlink> {
let res = sqlx::query_as(
r#"
SELECT id, link
SELECT id, link, long_url
FROM shorten
WHERE group_guid = $1 AND domain = $2 AND long_url = $3
LIMIT 1
Expand Down Expand Up @@ -140,6 +140,7 @@ impl FromRow<'_, SqliteRow> for Bitlink {
Ok(Self {
link: row.try_from::<&str, _, _>("link")?,
id: row.try_get("id")?,
long_url: row.try_from::<&str, _, _>("long_url")?,
})
}
}
Expand Down Expand Up @@ -194,6 +195,7 @@ mod tests {
Bitlink {
link: "https://bit.ly/4ePsyXN".parse().unwrap(),
id: "some-bitlink-id".to_string(),
long_url: "https://example.com".parse().unwrap(),
}
}

Expand Down
25 changes: 20 additions & 5 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::borrow::Cow;
use std::path::{Path, PathBuf};

use clap::builder::ArgPredicate;
use clap::{Args, Parser, Subcommand, ValueHint};
use clap::{Args, Parser, Subcommand, ValueEnum, ValueHint};
use url::Url;

use crate::config::{ConfigError, Options, APP};
Expand Down Expand Up @@ -30,7 +30,7 @@ pub struct Cli {
/// Equivalent to passing an empty `--cache-dir` path. Takes priority over `--cache-dir`.
#[arg(
long,
default_value = "false",
default_value_t = false,
overrides_with = "cache_dir",
env = "BITCLI_NO_CACHE"
)]
Expand All @@ -43,7 +43,7 @@ pub struct Cli {
/// is set to an empty path (which disables caching).
#[arg(
long,
default_value = "false",
default_value_t = false,
default_value_if("cache_dir", ArgPredicate::Equals("".into()), "false"),
conflicts_with = "no_cache",
env = "BITCLI_OFFLINE"
Expand Down Expand Up @@ -125,17 +125,25 @@ impl From<&Command> for Options {

#[derive(Args, Debug)]
pub struct ShortenArgs {
// TODO: --unordered (modify the output to `<long-url> <short-url>`)
// TODO: allow reading URL(s) from stdin
/// URLs to shorten
#[arg(num_args(1..))]
pub urls: Vec<Url>,

// TODO: min = 1 | NonZeroUsize
/// Maximum number of API requests in flight
#[arg(long, default_value = "16", env = "BITCLI_MAX_CONCURRENT")]
#[arg(long, default_value_t = 16, env = "BITCLI_MAX_CONCURRENT")]
pub max_concurrent: usize,

/// The type of the output ordering
///
/// - ordered: individual outputs follow the input order
///
/// - unordered: outputs follow an arbitrary order, but are printed together with
/// corresponding input URL
#[arg(long, default_value_t, value_enum, env = "BITCLI_ORDERING")]
pub ordering: Ordering,

/// The domain to create bitlinks under
#[arg(short, long, env = "BITCLI_DOMAIN")]
pub domain: Option<String>,
Expand All @@ -152,3 +160,10 @@ pub struct ShortenArgs {
#[arg(short, long, env = "BITCLI_GROUP_GUID")]
pub group_guid: Option<String>,
}

#[derive(Clone, Copy, Debug, Default, ValueEnum)]
pub enum Ordering {
#[default]
Ordered,
Unordered,
}
26 changes: 17 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use clap::Parser as _;
use futures_util::pin_mut;
use futures_util::stream::StreamExt as _;

mod api;
Expand All @@ -9,7 +8,7 @@ mod config;
mod error;

use api::Client;
use cli::{Cli, Command};
use cli::{Cli, Command, Ordering};
use config::Config;

macro_rules! crash_if_err {
Expand Down Expand Up @@ -38,13 +37,22 @@ async fn main() {

match cmd {
Command::Shorten(args) => {
let results = client.shorten(args.urls).await;

pin_mut!(results);

while let Some(result) = results.next().await {
let bitlink = crash_if_err! { result };
println!("{}", bitlink.link);
let mut results = client.shorten(args.urls, args.ordering);

match args.ordering {
Ordering::Ordered => {
while let Some(result) = results.next().await {
let bitlink = crash_if_err! { result };
println!("{}", bitlink.link);
}
}

Ordering::Unordered => {
while let Some(result) = results.next().await {
let bitlink = crash_if_err! { result };
println!("{}\t{}", bitlink.link, bitlink.long_url);
}
}
}
}
}
Expand Down

0 comments on commit 26dc0a4

Please sign in to comment.