mirror of
https://codeberg.org/Cyborus/forgejo-cli.git
synced 2024-11-27 04:03:48 +01:00
Merge pull request 'add wiki commands' (#105) from wiki into main
Reviewed-on: https://codeberg.org/Cyborus/forgejo-cli/pulls/105
This commit is contained in:
commit
96f72ee428
3 changed files with 229 additions and 59 deletions
|
@ -13,6 +13,7 @@ mod prs;
|
||||||
mod release;
|
mod release;
|
||||||
mod repo;
|
mod repo;
|
||||||
mod user;
|
mod user;
|
||||||
|
mod wiki;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
pub struct App {
|
pub struct App {
|
||||||
|
@ -30,6 +31,7 @@ pub enum Command {
|
||||||
Repo(repo::RepoCommand),
|
Repo(repo::RepoCommand),
|
||||||
Issue(issues::IssueCommand),
|
Issue(issues::IssueCommand),
|
||||||
Pr(prs::PrCommand),
|
Pr(prs::PrCommand),
|
||||||
|
Wiki(wiki::WikiCommand),
|
||||||
#[command(name = "whoami")]
|
#[command(name = "whoami")]
|
||||||
WhoAmI {
|
WhoAmI {
|
||||||
#[clap(long, short)]
|
#[clap(long, short)]
|
||||||
|
@ -61,6 +63,7 @@ async fn main() -> eyre::Result<()> {
|
||||||
Command::Repo(subcommand) => subcommand.run(&mut keys, host_name).await?,
|
Command::Repo(subcommand) => subcommand.run(&mut keys, host_name).await?,
|
||||||
Command::Issue(subcommand) => subcommand.run(&mut keys, host_name).await?,
|
Command::Issue(subcommand) => subcommand.run(&mut keys, host_name).await?,
|
||||||
Command::Pr(subcommand) => subcommand.run(&mut keys, host_name).await?,
|
Command::Pr(subcommand) => subcommand.run(&mut keys, host_name).await?,
|
||||||
|
Command::Wiki(subcommand) => subcommand.run(&mut keys, host_name).await?,
|
||||||
Command::WhoAmI { remote } => {
|
Command::WhoAmI { remote } => {
|
||||||
let url = repo::RepoInfo::get_current(host_name, None, remote.as_deref())
|
let url = repo::RepoInfo::get_current(host_name, None, remote.as_deref())
|
||||||
.wrap_err("could not find host, try specifying with --host")?
|
.wrap_err("could not find host, try specifying with --host")?
|
||||||
|
|
127
src/repo.rs
127
src/repo.rs
|
@ -600,65 +600,7 @@ impl RepoCommand {
|
||||||
|
|
||||||
let path = path.unwrap_or_else(|| PathBuf::from(format!("./{repo_name}")));
|
let path = path.unwrap_or_else(|| PathBuf::from(format!("./{repo_name}")));
|
||||||
|
|
||||||
let SpecialRender {
|
let local_repo = clone_repo(&repo_full_name, &clone_url, &path)?;
|
||||||
fancy,
|
|
||||||
hide_cursor,
|
|
||||||
show_cursor,
|
|
||||||
clear_line,
|
|
||||||
..
|
|
||||||
} = *crate::special_render();
|
|
||||||
|
|
||||||
let auth = auth_git2::GitAuthenticator::new();
|
|
||||||
let git_config = git2::Config::open_default()?;
|
|
||||||
|
|
||||||
let mut options = git2::FetchOptions::new();
|
|
||||||
let mut callbacks = git2::RemoteCallbacks::new();
|
|
||||||
callbacks.credentials(auth.credentials(&git_config));
|
|
||||||
|
|
||||||
if fancy {
|
|
||||||
print!("{hide_cursor}");
|
|
||||||
print!(" Preparing...");
|
|
||||||
let _ = std::io::stdout().flush();
|
|
||||||
|
|
||||||
callbacks.transfer_progress(|progress| {
|
|
||||||
print!("{clear_line}\r");
|
|
||||||
if progress.received_objects() == progress.total_objects() {
|
|
||||||
if progress.indexed_deltas() == progress.total_deltas() {
|
|
||||||
print!("Finishing up...");
|
|
||||||
} else {
|
|
||||||
let percent = 100.0 * (progress.indexed_deltas() as f64)
|
|
||||||
/ (progress.total_deltas() as f64);
|
|
||||||
print!(" Resolving... {percent:.01}%");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let bytes = progress.received_bytes();
|
|
||||||
let percent = 100.0 * (progress.received_objects() as f64)
|
|
||||||
/ (progress.total_objects() as f64);
|
|
||||||
print!(" Downloading... {percent:.01}%");
|
|
||||||
match bytes {
|
|
||||||
0..=1023 => print!(" ({}b)", bytes),
|
|
||||||
1024..=1048575 => print!(" ({:.01}kb)", (bytes as f64) / 1024.0),
|
|
||||||
1048576..=1073741823 => {
|
|
||||||
print!(" ({:.01}mb)", (bytes as f64) / 1048576.0)
|
|
||||||
}
|
|
||||||
1073741824.. => {
|
|
||||||
print!(" ({:.01}gb)", (bytes as f64) / 1073741824.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let _ = std::io::stdout().flush();
|
|
||||||
true
|
|
||||||
});
|
|
||||||
options.remote_callbacks(callbacks);
|
|
||||||
}
|
|
||||||
|
|
||||||
let local_repo = git2::build::RepoBuilder::new()
|
|
||||||
.fetch_options(options)
|
|
||||||
.clone(clone_url.as_str(), &path)?;
|
|
||||||
if fancy {
|
|
||||||
print!("{clear_line}{show_cursor}\r");
|
|
||||||
}
|
|
||||||
println!("Cloned {} into {}", repo_full_name, path.display());
|
|
||||||
|
|
||||||
if let Some(parent) = repo_data.parent.as_deref() {
|
if let Some(parent) = repo_data.parent.as_deref() {
|
||||||
let parent_clone_url = parent
|
let parent_clone_url = parent
|
||||||
|
@ -717,3 +659,70 @@ impl RepoCommand {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clone_repo(
|
||||||
|
repo_name: &str,
|
||||||
|
url: &url::Url,
|
||||||
|
path: &std::path::Path,
|
||||||
|
) -> eyre::Result<git2::Repository> {
|
||||||
|
let SpecialRender {
|
||||||
|
fancy,
|
||||||
|
hide_cursor,
|
||||||
|
show_cursor,
|
||||||
|
clear_line,
|
||||||
|
..
|
||||||
|
} = *crate::special_render();
|
||||||
|
|
||||||
|
let auth = auth_git2::GitAuthenticator::new();
|
||||||
|
let git_config = git2::Config::open_default()?;
|
||||||
|
|
||||||
|
let mut options = git2::FetchOptions::new();
|
||||||
|
let mut callbacks = git2::RemoteCallbacks::new();
|
||||||
|
callbacks.credentials(auth.credentials(&git_config));
|
||||||
|
|
||||||
|
if fancy {
|
||||||
|
print!("{hide_cursor}");
|
||||||
|
print!(" Preparing...");
|
||||||
|
let _ = std::io::stdout().flush();
|
||||||
|
|
||||||
|
callbacks.transfer_progress(|progress| {
|
||||||
|
print!("{clear_line}\r");
|
||||||
|
if progress.received_objects() == progress.total_objects() {
|
||||||
|
if progress.indexed_deltas() == progress.total_deltas() {
|
||||||
|
print!("Finishing up...");
|
||||||
|
} else {
|
||||||
|
let percent = 100.0 * (progress.indexed_deltas() as f64)
|
||||||
|
/ (progress.total_deltas() as f64);
|
||||||
|
print!(" Resolving... {percent:.01}%");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let bytes = progress.received_bytes();
|
||||||
|
let percent = 100.0 * (progress.received_objects() as f64)
|
||||||
|
/ (progress.total_objects() as f64);
|
||||||
|
print!(" Downloading... {percent:.01}%");
|
||||||
|
match bytes {
|
||||||
|
0..=1023 => print!(" ({}b)", bytes),
|
||||||
|
1024..=1048575 => print!(" ({:.01}kb)", (bytes as f64) / 1024.0),
|
||||||
|
1048576..=1073741823 => {
|
||||||
|
print!(" ({:.01}mb)", (bytes as f64) / 1048576.0)
|
||||||
|
}
|
||||||
|
1073741824.. => {
|
||||||
|
print!(" ({:.01}gb)", (bytes as f64) / 1073741824.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = std::io::stdout().flush();
|
||||||
|
true
|
||||||
|
});
|
||||||
|
options.remote_callbacks(callbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
let local_repo = git2::build::RepoBuilder::new()
|
||||||
|
.fetch_options(options)
|
||||||
|
.clone(url.as_str(), &path)?;
|
||||||
|
if fancy {
|
||||||
|
print!("{clear_line}{show_cursor}\r");
|
||||||
|
}
|
||||||
|
println!("Cloned {} into {}", repo_name, path.display());
|
||||||
|
Ok(local_repo)
|
||||||
|
}
|
||||||
|
|
158
src/wiki.rs
Normal file
158
src/wiki.rs
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use base64ct::Encoding;
|
||||||
|
use clap::{Args, Subcommand};
|
||||||
|
use eyre::{Context, OptionExt};
|
||||||
|
use forgejo_api::Forgejo;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
repo::{RepoArg, RepoInfo, RepoName},
|
||||||
|
SpecialRender,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Args, Clone, Debug)]
|
||||||
|
pub struct WikiCommand {
|
||||||
|
/// The local git remote that points to the repo to operate on.
|
||||||
|
#[clap(long, short = 'R')]
|
||||||
|
remote: Option<String>,
|
||||||
|
#[clap(subcommand)]
|
||||||
|
command: WikiSubcommand,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand, Clone, Debug)]
|
||||||
|
pub enum WikiSubcommand {
|
||||||
|
Contents {
|
||||||
|
repo: Option<RepoArg>,
|
||||||
|
},
|
||||||
|
View {
|
||||||
|
#[clap(long, short)]
|
||||||
|
repo: Option<RepoArg>,
|
||||||
|
page: String,
|
||||||
|
},
|
||||||
|
Clone {
|
||||||
|
repo: Option<RepoArg>,
|
||||||
|
#[clap(long, short)]
|
||||||
|
path: Option<PathBuf>,
|
||||||
|
},
|
||||||
|
Browse {
|
||||||
|
#[clap(long, short)]
|
||||||
|
repo: Option<RepoArg>,
|
||||||
|
page: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WikiCommand {
|
||||||
|
pub async fn run(self, keys: &mut crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> {
|
||||||
|
use WikiSubcommand::*;
|
||||||
|
|
||||||
|
let repo = RepoInfo::get_current(host_name, self.repo(), self.remote.as_deref())?;
|
||||||
|
let api = keys.get_api(repo.host_url()).await?;
|
||||||
|
let repo = repo
|
||||||
|
.name()
|
||||||
|
.ok_or_else(|| eyre::eyre!("couldn't guess repo"))?;
|
||||||
|
|
||||||
|
match self.command {
|
||||||
|
Contents { repo: _ } => wiki_contents(&repo, &api).await?,
|
||||||
|
View { repo: _, page } => view_wiki_page(&repo, &api, &*page).await?,
|
||||||
|
Clone { repo: _, path } => clone_wiki(&repo, &api, path).await?,
|
||||||
|
Browse { repo: _, page } => browse_wiki_page(&repo, &api, &*page).await?,
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn repo(&self) -> Option<&RepoArg> {
|
||||||
|
use WikiSubcommand::*;
|
||||||
|
match &self.command {
|
||||||
|
Contents { repo } | View { repo, .. } | Clone { repo, .. } | Browse { repo, .. } => {
|
||||||
|
repo.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn wiki_contents(repo: &RepoName, api: &Forgejo) -> eyre::Result<()> {
|
||||||
|
let SpecialRender { bullet, .. } = *crate::special_render();
|
||||||
|
|
||||||
|
let query = forgejo_api::structs::RepoGetWikiPagesQuery {
|
||||||
|
page: None,
|
||||||
|
limit: None,
|
||||||
|
};
|
||||||
|
let pages = api
|
||||||
|
.repo_get_wiki_pages(repo.owner(), repo.name(), query)
|
||||||
|
.await?;
|
||||||
|
for page in pages {
|
||||||
|
let title = page
|
||||||
|
.title
|
||||||
|
.as_deref()
|
||||||
|
.ok_or_eyre("page does not have title")?;
|
||||||
|
println!("{bullet} {title}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn view_wiki_page(repo: &RepoName, api: &Forgejo, page: &str) -> eyre::Result<()> {
|
||||||
|
let SpecialRender { bold, reset, .. } = *crate::special_render();
|
||||||
|
|
||||||
|
let page = api
|
||||||
|
.repo_get_wiki_page(repo.owner(), repo.name(), page)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let title = page
|
||||||
|
.title
|
||||||
|
.as_deref()
|
||||||
|
.ok_or_eyre("page does not have title")?;
|
||||||
|
println!("{bold}{title}{reset}");
|
||||||
|
println!();
|
||||||
|
|
||||||
|
let contents_b64 = page
|
||||||
|
.content_base64
|
||||||
|
.as_deref()
|
||||||
|
.ok_or_eyre("page does not have content")?;
|
||||||
|
let contents = String::from_utf8(base64ct::Base64::decode_vec(contents_b64)?)
|
||||||
|
.wrap_err("page content is not utf-8")?;
|
||||||
|
|
||||||
|
println!("{}", crate::markdown(&contents));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn browse_wiki_page(repo: &RepoName, api: &Forgejo, page: &str) -> eyre::Result<()> {
|
||||||
|
let page = api
|
||||||
|
.repo_get_wiki_page(repo.owner(), repo.name(), page)
|
||||||
|
.await?;
|
||||||
|
let html_url = page
|
||||||
|
.html_url
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_eyre("page does not have html url")?;
|
||||||
|
open::that(html_url.as_str())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn clone_wiki(repo: &RepoName, api: &Forgejo, path: Option<PathBuf>) -> eyre::Result<()> {
|
||||||
|
let repo_data = api.repo_get(repo.owner(), repo.name()).await?;
|
||||||
|
let clone_url = repo_data
|
||||||
|
.clone_url
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_eyre("repo does not have clone url")?;
|
||||||
|
let git_stripped = clone_url
|
||||||
|
.as_str()
|
||||||
|
.strip_suffix(".git")
|
||||||
|
.unwrap_or(clone_url.as_str());
|
||||||
|
let clone_url = url::Url::parse(&format!("{}.wiki.git", git_stripped))?;
|
||||||
|
|
||||||
|
let repo_name = repo_data
|
||||||
|
.name
|
||||||
|
.as_deref()
|
||||||
|
.ok_or_eyre("repo does not have name")?;
|
||||||
|
let repo_full_name = repo_data
|
||||||
|
.full_name
|
||||||
|
.as_deref()
|
||||||
|
.ok_or_eyre("repo does not have full name")?;
|
||||||
|
let name = format!("{}'s wiki", repo_full_name);
|
||||||
|
|
||||||
|
let path = path.unwrap_or_else(|| PathBuf::from(format!("./{repo_name}-wiki")));
|
||||||
|
|
||||||
|
crate::repo::clone_repo(&name, &clone_url, &path)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in a new issue