src/commands/update/hub.rs
use crate::{ElvscArgs, RemoteOrLocal, check_output};
use std::path::Path;
use askama::Template;
use jj_cli::{
cli_util::CommandHelper,
command_error::{CommandError, internal_error_with_message, user_error_with_message},
ui::Ui,
};
#[rustfmt::skip]
const ASSETS: &[(&str, &[u8])] = &[
("style.css", include_bytes!("../../../assets/style.css")),
("ZedMono.woff2", include_bytes!("../../../assets/ZedMono.woff2")),
("ZedSansBold.woff2", include_bytes!("../../../assets/ZedSansBold.woff2")),
("ZedSansItalic.woff2", include_bytes!("../../../assets/ZedSansItalic.woff2")),
("ZedSansRegular.woff2", include_bytes!("../../../assets/ZedSansRegular.woff2")),
];
pub(super) fn write_assets(output: &Path) -> Result<(), CommandError> {
for (name, bytes) in ASSETS {
std::fs::write(output.join(name), bytes)?;
}
Ok(())
}
#[derive(Debug, Clone, serde::Deserialize)]
pub struct RepositorySummary {
pub name: String,
pub commits: usize,
pub latest: String,
pub description: String,
}
const NUSHELL_GET_SUMMARIES: &str = r#"
| where { |it| $it.type == dir }
| insert commits { |it| (try { git --git-dir $it.name rev-list --count trunk } catch { try { jj -R $it.name log -r 'ancestors(remote_bookmarks(trunk))' --count } catch { 0 } } | into int) + 1 }
| insert description { |it| try { git --git-dir $it.name show trunk:DESCRIPTION } catch { try { jj -R $it.name file show -r 'remote_bookmarks(trunk)' DESCRIPTION } catch { '<no description>' } } }
| insert latest { |it| try { git --git-dir $it.name log -1 --format=%cd trunk | format date '%Y-%m-%d %H:%M' } catch { '<no date>' } }
| update name { |it| $it.name | path basename }
| select name latest commits description
| to json
"#;
pub fn get_hub_summaries(hub: &RemoteOrLocal<'_>) -> Result<Vec<RepositorySummary>, CommandError> {
let path = hub.path().display().to_string();
let script = format!("ls {path} {NUSHELL_GET_SUMMARIES}");
let output = check_output(hub.shell_command(&script).output()?)?;
serde_json::from_slice(&output.stdout)
.map_err(|e| user_error_with_message("Couldn't parse hub summaries json", e))
}
pub async fn run(ui: &mut Ui, ch: &CommandHelper) -> Result<(), CommandError> {
let args = ElvscArgs::from_config(ch)?;
let tmp = tempfile::tempdir()?;
let repositories = get_hub_summaries(&(&args.hub).into())?;
write_frontend(tmp.path(), &args.public, &repositories)?;
write_assets(tmp.path())?;
super::rsync(ui, tmp.path(), &args.output, None)
}
#[derive(askama::Template)]
#[template(path = "hub.html")]
struct HubTemplate<'a> {
public: &'a str,
repositories: &'a [RepositorySummary],
}
pub fn write_frontend(
output: &Path,
public: &str,
repositories: &[RepositorySummary],
) -> Result<(), CommandError> {
let html = HubTemplate {
public,
repositories,
}
.render()
.map_err(|e| internal_error_with_message("Couldn't render html", e))?;
std::fs::write(output.join("index.html"), html)?;
Ok(())
}