summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCyborus <cyborus@noreply.codeberg.org>2024-08-08 01:45:51 +0200
committerCyborus <cyborus@noreply.codeberg.org>2024-08-08 01:45:51 +0200
commit96f72ee428bc62f131ecb8654db6cd947fa82aa9 (patch)
tree03d807125b2f388aa8b31629532f50b9cb1a92fc
parentMerge pull request 'update `forgejo-api` to v0.4.1' (#104) from api-0.4.1 int... (diff)
parentrefactor: remove `WikiCommand::no_repo_error` (diff)
downloadforgejo-cli-96f72ee428bc62f131ecb8654db6cd947fa82aa9.tar.xz
forgejo-cli-96f72ee428bc62f131ecb8654db6cd947fa82aa9.zip
Merge pull request 'add wiki commands' (#105) from wiki into main
Reviewed-on: https://codeberg.org/Cyborus/forgejo-cli/pulls/105
-rw-r--r--src/main.rs3
-rw-r--r--src/repo.rs127
-rw-r--r--src/wiki.rs158
3 files changed, 229 insertions, 59 deletions
diff --git a/src/main.rs b/src/main.rs
index 59a70da..c26081f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -13,6 +13,7 @@ mod prs;
mod release;
mod repo;
mod user;
+mod wiki;
#[derive(Parser, Debug)]
pub struct App {
@@ -30,6 +31,7 @@ pub enum Command {
Repo(repo::RepoCommand),
Issue(issues::IssueCommand),
Pr(prs::PrCommand),
+ Wiki(wiki::WikiCommand),
#[command(name = "whoami")]
WhoAmI {
#[clap(long, short)]
@@ -61,6 +63,7 @@ async fn main() -> eyre::Result<()> {
Command::Repo(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::Wiki(subcommand) => subcommand.run(&mut keys, host_name).await?,
Command::WhoAmI { remote } => {
let url = repo::RepoInfo::get_current(host_name, None, remote.as_deref())
.wrap_err("could not find host, try specifying with --host")?
diff --git a/src/repo.rs b/src/repo.rs
index ee67b9b..593caba 100644
--- a/src/repo.rs
+++ b/src/repo.rs
@@ -600,65 +600,7 @@ impl RepoCommand {
let path = path.unwrap_or_else(|| PathBuf::from(format!("./{repo_name}")));
- 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(clone_url.as_str(), &path)?;
- if fancy {
- print!("{clear_line}{show_cursor}\r");
- }
- println!("Cloned {} into {}", repo_full_name, path.display());
+ let local_repo = clone_repo(&repo_full_name, &clone_url, &path)?;
if let Some(parent) = repo_data.parent.as_deref() {
let parent_clone_url = parent
@@ -717,3 +659,70 @@ impl RepoCommand {
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)
+}
diff --git a/src/wiki.rs b/src/wiki.rs
new file mode 100644
index 0000000..63d344f
--- /dev/null
+++ b/src/wiki.rs
@@ -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(())
+}