use anyhow::Result;
use crate::{
api::{self, GhResult},
args::Args,
export_types::{PackageExport, PackageGithubInput, Snapshot},
fmt,
types::OutputFormat,
};
pub async fn run(args: &Args) -> Result<()> {
match args.output.as_ref().expect("output mode must be set") {
OutputFormat::Json => run_json(args).await,
OutputFormat::Compact => run_compact(args).await,
OutputFormat::Detail => run_detail(args).await,
}
}
async fn run_json(args: &Args) -> Result<()> {
let (packages, _) = api::fetch_packages(
args.search.as_deref().unwrap_or(""),
args.sort.api_param(),
args.language,
1,
)
.await?;
let snapshot = Snapshot::build(
&packages,
&args.language.to_string(),
args.search.as_deref().unwrap_or(""),
args.sort.api_param(),
);
print_json(&snapshot)
}
async fn run_compact(args: &Args) -> Result<()> {
let (packages, _) = api::fetch_packages(
args.search.as_deref().unwrap_or(""),
args.sort.api_param(),
args.language,
1,
)
.await?;
let snapshot = Snapshot::build(
&packages,
&args.language.to_string(),
args.search.as_deref().unwrap_or(""),
args.sort.api_param(),
);
print_compact(&snapshot);
Ok(())
}
async fn run_detail(args: &Args) -> Result<()> {
let pkg = match &args.package {
Some(name) => api::fetch_package(name).await?,
None => {
let (pkgs, _) = api::fetch_packages(
args.search.as_deref().unwrap_or(""),
args.sort.api_param(),
args.language,
1,
)
.await?;
pkgs.into_iter()
.next()
.ok_or_else(|| anyhow::anyhow!("no packages found"))?
}
};
let github = match &pkg.repo_url {
Some(url) if url.contains("github.com") => {
let token = api::github_token();
match api::fetch_github_stats(url, token.as_deref()).await? {
GhResult::Ok(stats) => Some(PackageGithubInput {
stats,
fetched_at: chrono::Utc::now().to_rfc3339(),
source: "live".into(),
}),
_ => None,
}
}
_ => None,
};
let export = PackageExport::from_package(&pkg, github);
print_detail(&export);
Ok(())
}
pub fn print_json(snapshot: &Snapshot) -> Result<()> {
println!("{}", serde_json::to_string_pretty(snapshot)?);
Ok(())
}
pub fn print_compact(snapshot: &Snapshot) {
let lang = &snapshot.query.language;
let search = if snapshot.query.search.is_empty() {
"(all)".to_string()
} else {
snapshot.query.search.clone()
};
let date = &snapshot.meta.fetched_at[..10];
let sort = &snapshot.query.sort;
println!("## {lang} packages — {search} (sort: {sort}) — {date}\n");
println!(
"| {:<20} | {:>7} | {:>5} | {:>9} | {:>8} | {:>5} | {:<10} |",
"package", "version", "lang", "dl_recent", "dl_total", "stars", "updated"
);
println!(
"|{:-<22}|{:-<9}|{:-<7}|{:-<11}|{:-<10}|{:-<7}|{:-<12}|",
"", "", "", "", "", "", ""
);
for p in &snapshot.packages {
let stars = p
.github
.as_ref()
.map(|g| fmt::dl_full(g.stars as u64))
.unwrap_or_else(|| "-".into());
println!(
"| {:<20} | {:>7} | {:>5} | {:>9} | {:>8} | {:>5} | {:<10} |",
p.id,
p.release.latest,
&p.language[..p.language.len().min(5)],
fmt::dl_full(p.downloads.recent_90d),
fmt::dl_short(p.downloads.all_time),
stars,
fmt::date(&p.release.updated_at),
);
}
}
pub fn print_detail(p: &PackageExport) {
println!("### {} — v{}\n", p.id, p.release.latest);
if !p.description.is_empty() {
println!("**Description:** {}\n", p.description);
}
println!("- **Language:** {}", p.language);
println!(
"- **Downloads:** {} total · {} recent (90d)",
fmt::dl_full(p.downloads.all_time),
fmt::dl_full(p.downloads.recent_90d),
);
if let Some(gh) = &p.github {
println!(
"- **GitHub:** ★ {} · ⑂ {} · ⊙ {} open issues",
fmt::dl_full(gh.stars as u64),
fmt::dl_full(gh.forks as u64),
gh.open_issues,
);
}
println!(
"- **Updated:** {} · Published: {}",
fmt::date(&p.release.updated_at),
fmt::date(&p.release.published_at),
);
if !p.licenses.is_empty() {
println!("- **License:** {}", p.licenses.join(", "));
}
if let Some(docs) = &p.links.docs {
println!("- **Docs:** {docs}");
}
if let Some(repo) = &p.links.repository {
println!("- **Repo:** {repo}");
}
}