1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
//! Functions for interacting with GitHub via the `gh` CLI
use std::error::Error;
use std::ffi::{OsStr, OsString};
use std::path::PathBuf;
use std::sync::OnceLock;
pub mod gh_cli;
pub mod gh_cli_fake;
pub mod util;
/// Get the GitHub CLI and initialize it with a default repository
/// If `fake` is true, a fake GitHub CLI is returned.
/// The fake GitHub CLI is used for testing and does not interact with GitHub
///
/// # Arguments
///
/// * `repo` - The default repository to use
/// * `fake` - If true, a fake GitHub CLI is returned
///
/// # Returns
///
/// [`Box<dyn GitHub>`](GitHub) - The GitHub CLI interface
///
/// # Example
///
/// ```
/// # use gh_workflow_parser::gh::init_github_cli;
/// let github_cli = init_github_cli("https://example.com/repo".to_string(), false);
/// ```
pub fn init_github_cli(repo: String, fake: bool) -> Box<dyn GitHub> {
if fake {
Box::new(gh_cli_fake::GitHubCliFake::new(repo))
} else {
Box::new(gh_cli::GitHubCli::new(repo))
}
}
/// Trait describing the methods that the GitHub CLI should implement
pub trait GitHub {
/// Get the summary of a run in a GitHub repository, if `repo` is `None` the default repository is used
/// Returns the summary as a [String]
fn run_summary(&self, repo: Option<&str>, run_id: &str) -> Result<String, Box<dyn Error>>;
/// Get the log of a failed job in a GitHub repository, if `repo` is `None` the default repository is used
/// Returns the log as a [String]
fn failed_job_log(&self, repo: Option<&str>, job_id: &str) -> Result<String, Box<dyn Error>>;
/// Create an issue in a GitHub repository, if `repo` is `None` the default repository is used
fn create_issue(
&self,
repo: Option<&str>,
title: &str,
body: &str,
labels: &[String],
) -> Result<(), Box<dyn Error>>;
/// Get the bodies of open issues with a specific label in a GitHub repository, if `repo` is `None` the default repository is used
/// Returns [`Vec<String>`](Vec) of issue bodies
fn issue_bodies_open_with_label(
&self,
repo: Option<&str>,
label: &str,
) -> Result<Vec<String>, Box<dyn Error>>;
/// Get all labels in a GitHub repository, if `repo` is `None` the default repository is used
/// Returns [`Vec<String>`](Vec) of GitHub labels
fn all_labels(&self, repo: Option<&str>) -> Result<Vec<String>, Box<dyn Error>>;
/// Create a label in a GitHub repository, if `repo` is `None` the default repository is used
/// The color should be a 6 character hex code (e.g. "FF0000")
/// if `force` is true and the label already exists, it will be overwritten
fn create_label(
&self,
repo: Option<&str>,
name: &str,
color: &str,
description: &str,
force: bool,
) -> Result<(), Box<dyn Error>>;
/// Get the default repository for the GitHub CLI
fn default_repo(&self) -> &str;
}
include!(concat!(env!("OUT_DIR"), "/include_gh_cli.rs"));
pub static GITHUB_CLI: OnceLock<OsString> = OnceLock::new();
pub fn gh_cli() -> &'static OsStr {
GITHUB_CLI.get_or_init(|| {
let gh_cli_path = gh_cli_first_time_setup().unwrap();
OsString::from(gh_cli_path)
})
}
pub fn gh_cli_first_time_setup() -> Result<PathBuf, Box<dyn Error>> {
let mut path = std::env::current_exe()?;
path.pop();
path.push("gh-workflow-parser-deps");
if !path.exists() {
std::fs::create_dir(&path)?;
}
let gh_cli_path = path.join("gh_cli");
if !gh_cli_path.exists() {
log::debug!("the gh_cli file at {gh_cli_path:?} doesn't exist. Creating it...");
// first decompress the gh-cli binary blob
let gh_cli_bytes = GH_CLI_BYTES;
log::trace!("gh_cli_bytes size: {}", gh_cli_bytes.len());
let decompressed_gh_cli = crate::util::bzip2_decompress(gh_cli_bytes)?;
log::trace!("decompressed_gh_cli size: {}", decompressed_gh_cli.len());
// Write the gh_cli file to the gh_cli_path
std::fs::write(&gh_cli_path, decompressed_gh_cli)?;
#[cfg(target_os = "linux")]
crate::util::set_linux_file_permissions(&gh_cli_path, 0o755)?;
log::debug!("gh_cli file written to {gh_cli_path:?}");
} else {
log::debug!(
"the gh_cli file at {gh_cli_path:?} already exists. Skipping first time setup..."
);
}
Ok(gh_cli_path)
}