use crate::cli::{Theme, UserOpts};
use crate::data::user::UserProfile;
use crate::display::theme::ThemeColors;
use crate::display::*;
pub fn render(profile: &UserProfile, opts: &UserOpts, theme: Theme, no_color: bool) {
let colors = ThemeColors::from_theme(theme);
if opts.detailed_languages() {
if let Some(ref langs) = profile.languages {
language_bar::render_detail_table(langs, &profile.login, &colors, no_color);
} else {
println!("No language data available.");
}
return;
}
let width = card_width();
let inner = width - 4;
let mut lines = Vec::new();
let title = if no_color {
format!("{} @ GitHub", profile.login)
} else {
format!(
"{} {} {}",
colors.title(&profile.login),
colors.muted("@"),
colors.muted("GitHub")
)
};
lines.push(title);
let sep = "─".repeat(profile.login.len() + 9);
lines.push(if no_color { sep } else { colors.muted(&sep) });
let fields: Vec<(&str, Option<String>)> = vec![
("Name", profile.name.clone().filter(|s| !s.is_empty())),
("Bio", profile.bio.clone().filter(|s| !s.is_empty())),
(
"Location",
profile.location.clone().filter(|s| !s.is_empty()),
),
("Company", profile.company.clone().filter(|s| !s.is_empty())),
("Blog", profile.blog.clone().filter(|s| !s.is_empty())),
("Twitter", profile.twitter.clone().map(|t| format!("@{t}"))),
("Joined", Some(profile.joined.clone())),
(
"Followers",
Some(format!(
"{} Following: {}",
format_number(profile.followers),
format_number(profile.following)
)),
),
];
for (label, value) in &fields {
if let Some(val) = value {
let formatted = if no_color {
format!("{:<12}{}", format!("{}:", label), val)
} else {
format!(
"{}{}",
colors.label(&format!("{:<12}", format!("{}:", label))),
colors.value(val)
)
};
lines.push(formatted);
}
}
if opts.show_contributions() || opts.show_full_card() {
lines.push(String::new());
let label_w = 14;
if let Some(ref c) = profile.contributions {
let stats_line = if no_color {
format!(
"{:<label_w$}{:<10}{:<label_w$}{}",
"Stars:",
format_number(profile.stats.total_stars),
"PRs:",
format_number(c.total_prs)
)
} else {
format!(
"{}{:<10}{}{}",
colors.label(&format!("{:<label_w$}", "Stars:")),
format_number(profile.stats.total_stars),
colors.label(&format!("{:<label_w$}", "PRs:")),
colors.value(&format_number(c.total_prs))
)
};
lines.push(stats_line);
let stats_line2 = if no_color {
format!(
"{:<label_w$}{:<10}{:<label_w$}{}",
"Commits:",
format_number(c.total_commits),
"Issues:",
format_number(c.total_issues)
)
} else {
format!(
"{}{:<10}{}{}",
colors.label(&format!("{:<label_w$}", "Commits:")),
format_number(c.total_commits),
colors.label(&format!("{:<label_w$}", "Issues:")),
colors.value(&format_number(c.total_issues))
)
};
lines.push(stats_line2);
let reviews = if no_color {
format!("{:<label_w$}{}", "Reviews:", format_number(c.total_reviews))
} else {
format!(
"{}{}",
colors.label(&format!("{:<label_w$}", "Reviews:")),
colors.value(&format_number(c.total_reviews))
)
};
lines.push(reviews);
let total = if no_color {
format!(
"{:<label_w$}{} (last year)",
"Contributions:",
format_number(c.total_contributions)
)
} else {
format!(
"{}{}{}",
colors.label(&format!("{:<label_w$}", "Contributions:")),
colors.accent(&format_number(c.total_contributions)),
colors.muted(" (last year)")
)
};
lines.push(total);
} else {
let stars = if no_color {
format!(
"{:<label_w$}{:<10}{:<label_w$}{}",
"Stars:",
format_number(profile.stats.total_stars),
"Repos:",
format_number(profile.stats.total_repos)
)
} else {
format!(
"{}{:<10}{}{}",
colors.label(&format!("{:<label_w$}", "Stars:")),
format_number(profile.stats.total_stars),
colors.label(&format!("{:<label_w$}", "Repos:")),
colors.value(&format_number(profile.stats.total_repos))
)
};
lines.push(stars);
let forks = if no_color {
format!(
"{:<label_w$}{}",
"Forks:",
format_number(profile.stats.total_forks)
)
} else {
format!(
"{}{}",
colors.label(&format!("{:<label_w$}", "Forks:")),
colors.value(&format_number(profile.stats.total_forks))
)
};
lines.push(forks);
}
}
if opts.show_languages()
&& let Some(ref langs) = profile.languages
&& !langs.entries.is_empty()
{
lines.push(String::new());
let header = if no_color {
"Languages".to_string()
} else {
colors.title("Languages")
};
lines.push(header);
let bar_lines = language_bar::render_bar(langs, inner.min(40), &colors, no_color);
lines.extend(bar_lines);
}
if opts.show_streaks()
&& let Some(ref s) = profile.streaks
{
lines.push(String::new());
let header = if no_color {
"Streaks".to_string()
} else {
colors.title("Streaks")
};
lines.push(header);
let streak_lines = streak::render_streaks(s, &colors, no_color);
lines.extend(streak_lines);
}
if opts.show_repos()
&& let Some(ref repos) = profile.top_repos
&& !repos.is_empty()
{
lines.push(String::new());
let header = if no_color {
"Top Repositories".to_string()
} else {
colors.title("Top Repositories")
};
lines.push(header);
for r in repos {
let lang = r.language.as_deref().unwrap_or("");
let private_tag = if r.is_private { " 🔒" } else { "" };
let line = if no_color {
format!(
" {}{private_tag} ★{} ⑂{} {lang}",
r.name,
format_number(r.stars),
format_number(r.forks)
)
} else {
format!(
" {}{} {}{} {}",
colors.accent(&r.name),
if r.is_private {
colors.muted(" 🔒")
} else {
String::new()
},
colors.value(&format!("★{}", format_number(r.stars))),
colors.muted(&format!(" ⑂{}", format_number(r.forks))),
colors.muted(lang)
)
};
lines.push(line);
}
}
println!("{}", top_border(width, &colors, no_color));
for line in &lines {
println!("{}", card_line(line, width, &colors, no_color));
}
println!("{}", bottom_border(width, &colors, no_color));
}